mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: allow css configuration from package.json or config file
This commit is contained in:
parent
bf0e35be37
commit
d61bf9bf19
@ -22,6 +22,7 @@
|
|||||||
- Regex-based configurations now have ordered entries and use the first match found.
|
- Regex-based configurations now have ordered entries and use the first match found.
|
||||||
- When starting the server through code, it is now possible to provide CLI value bindings as well in `AppRunner`.
|
- When starting the server through code, it is now possible to provide CLI value bindings as well in `AppRunner`.
|
||||||
- Support for Node v12 was dropped.
|
- Support for Node v12 was dropped.
|
||||||
|
- The server configuration settings can be set from the package.json or .community-solid-server.config.json/.js files.
|
||||||
|
|
||||||
### Data migration
|
### Data migration
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ the [changelog](https://github.com/CommunitySolidServer/CommunitySolidServer/blo
|
|||||||
* [How to use the Identity Provider](usage/identity-provider.md)
|
* [How to use the Identity Provider](usage/identity-provider.md)
|
||||||
* [How to automate authentication](usage/client-credentials.md)
|
* [How to automate authentication](usage/client-credentials.md)
|
||||||
* [How to automatically seed pods on startup](usage/seeding-pods.md)
|
* [How to automatically seed pods on startup](usage/seeding-pods.md)
|
||||||
|
* [Using the CSS as a development server in another project](usage/dev-configuration.md)
|
||||||
|
|
||||||
## What the internals look like
|
## What the internals look like
|
||||||
|
|
||||||
|
49
documentation/markdown/usage/dev-configuration.md
Normal file
49
documentation/markdown/usage/dev-configuration.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Configuring the CSS as a development server in another project
|
||||||
|
|
||||||
|
It can be useful to use the CSS as local server to develop Solid applications against.
|
||||||
|
As an alternative to using CLI arguments, or environment variables, the CSS can be configured in the `package.json` as follows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": "true",
|
||||||
|
"config": {
|
||||||
|
"community-solid-server": {
|
||||||
|
"port": 3001,
|
||||||
|
"loggingLevel": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev:pod": "community-solid-server"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@solid/community-server": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These parameters will then be used when the `community-solid-server`
|
||||||
|
command is executed as an npm script (as shown in the example above).
|
||||||
|
Or whenever the `community-solid-server` command is executed in the same
|
||||||
|
folder as the `package.json`.
|
||||||
|
|
||||||
|
Alternatively, the configuration parameters may be placed in a configuration file named
|
||||||
|
`.community-solid-server.config.json` as follows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"port": 3001,
|
||||||
|
"loggingLevel": "error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The config may also be written in JavaScript with the config as the default export
|
||||||
|
such as the following `.community-solid-server.config.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
port: 3001,
|
||||||
|
loggingLevel: "error"
|
||||||
|
};
|
||||||
|
```
|
@ -1,13 +1,15 @@
|
|||||||
/* eslint-disable unicorn/no-process-exit */
|
/* eslint-disable unicorn/no-process-exit */
|
||||||
|
import { existsSync } from 'fs';
|
||||||
import type { WriteStream } from 'tty';
|
import type { WriteStream } from 'tty';
|
||||||
import type { IComponentsManagerBuilderOptions } from 'componentsjs';
|
import type { IComponentsManagerBuilderOptions } from 'componentsjs';
|
||||||
import { ComponentsManager } from 'componentsjs';
|
import { ComponentsManager } from 'componentsjs';
|
||||||
|
import { readJSON } from 'fs-extra';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { LOG_LEVELS } from '../logging/LogLevel';
|
import { LOG_LEVELS } from '../logging/LogLevel';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import { createErrorMessage, isError } from '../util/errors/ErrorUtil';
|
import { createErrorMessage, isError } from '../util/errors/ErrorUtil';
|
||||||
import { InternalServerError } from '../util/errors/InternalServerError';
|
import { InternalServerError } from '../util/errors/InternalServerError';
|
||||||
import { resolveModulePath, resolveAssetPath } from '../util/PathUtil';
|
import { resolveModulePath, resolveAssetPath, joinFilePath } from '../util/PathUtil';
|
||||||
import type { App } from './App';
|
import type { App } from './App';
|
||||||
import type { CliExtractor } from './cli/CliExtractor';
|
import type { CliExtractor } from './cli/CliExtractor';
|
||||||
import type { CliResolver } from './CliResolver';
|
import type { CliResolver } from './CliResolver';
|
||||||
@ -135,7 +137,7 @@ export class AppRunner {
|
|||||||
*/
|
*/
|
||||||
public async createCli(argv: CliArgv = process.argv): Promise<App> {
|
public async createCli(argv: CliArgv = process.argv): Promise<App> {
|
||||||
// Parse only the core CLI arguments needed to load the configuration
|
// Parse only the core CLI arguments needed to load the configuration
|
||||||
const yargv = yargs(argv.slice(2))
|
let yargv = yargs(argv.slice(2))
|
||||||
.usage('node ./bin/server.js [args]')
|
.usage('node ./bin/server.js [args]')
|
||||||
.options(CORE_CLI_PARAMETERS)
|
.options(CORE_CLI_PARAMETERS)
|
||||||
// We disable help here as it would only show the core parameters
|
// We disable help here as it would only show the core parameters
|
||||||
@ -143,6 +145,12 @@ export class AppRunner {
|
|||||||
// We also read from environment variables
|
// We also read from environment variables
|
||||||
.env(ENV_VAR_PREFIX);
|
.env(ENV_VAR_PREFIX);
|
||||||
|
|
||||||
|
const settings = await this.getPackageSettings();
|
||||||
|
|
||||||
|
if (typeof settings !== 'undefined') {
|
||||||
|
yargv = yargv.default<object>(settings);
|
||||||
|
}
|
||||||
|
|
||||||
const params = await yargv.parse();
|
const params = await yargv.parse();
|
||||||
|
|
||||||
const loaderProperties = {
|
const loaderProperties = {
|
||||||
@ -165,12 +173,45 @@ export class AppRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the CLI components and use them to generate values for the Components.js variables
|
// Build the CLI components and use them to generate values for the Components.js variables
|
||||||
const variables = await this.cliToVariables(componentsManager, argv);
|
const variables = await this.cliToVariables(componentsManager, argv, settings);
|
||||||
|
|
||||||
// Build and start the actual server application using the generated variable values
|
// Build and start the actual server application using the generated variable values
|
||||||
return await this.createApp(componentsManager, variables);
|
return await this.createApp(componentsManager, variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves settings from package.json or configuration file when
|
||||||
|
* part of an npm project.
|
||||||
|
* @returns The settings defined in the configuration file
|
||||||
|
*/
|
||||||
|
public async getPackageSettings(): Promise<undefined | Record<string, unknown>> {
|
||||||
|
// Only try and retrieve config file settings if there is a package.json in the
|
||||||
|
// scope of the current directory
|
||||||
|
const packageJsonPath = joinFilePath(process.cwd(), 'package.json');
|
||||||
|
if (!existsSync(packageJsonPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First see if there is a dedicated .json configuration file
|
||||||
|
const cssConfigPath = joinFilePath(process.cwd(), '.community-solid-server.config.json');
|
||||||
|
if (existsSync(cssConfigPath)) {
|
||||||
|
return readJSON(cssConfigPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next see if there is a dedicated .js file
|
||||||
|
const cssConfigPathJs = joinFilePath(process.cwd(), '.community-solid-server.config.js');
|
||||||
|
if (existsSync(cssConfigPathJs)) {
|
||||||
|
return import(cssConfigPathJs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally try and read from the config.community-solid-server
|
||||||
|
// field in the root package.json
|
||||||
|
const pkg = await readJSON(packageJsonPath);
|
||||||
|
if (typeof pkg.config?.['community-solid-server'] === 'object') {
|
||||||
|
return pkg.config['community-solid-server'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the Components Manager that will be used for instantiating.
|
* Creates the Components Manager that will be used for instantiating.
|
||||||
*/
|
*/
|
||||||
@ -189,11 +230,14 @@ export class AppRunner {
|
|||||||
* Handles the first Components.js instantiation.
|
* Handles the first Components.js instantiation.
|
||||||
* Uses it to extract the CLI shorthand values and use those to create variable bindings.
|
* Uses it to extract the CLI shorthand values and use those to create variable bindings.
|
||||||
*/
|
*/
|
||||||
private async cliToVariables(componentsManager: ComponentsManager<CliResolver>, argv: CliArgv):
|
private async cliToVariables(
|
||||||
Promise<VariableBindings> {
|
componentsManager: ComponentsManager<CliResolver>,
|
||||||
|
argv: CliArgv,
|
||||||
|
settings?: Record<string, unknown>,
|
||||||
|
): Promise<VariableBindings> {
|
||||||
const cliResolver = await this.createCliResolver(componentsManager);
|
const cliResolver = await this.createCliResolver(componentsManager);
|
||||||
const shorthand = await this.extractShorthand(cliResolver.cliExtractor, argv);
|
const shorthand = await this.extractShorthand(cliResolver.cliExtractor, argv);
|
||||||
return await this.resolveShorthand(cliResolver.shorthandResolver, shorthand);
|
return await this.resolveShorthand(cliResolver.shorthandResolver, { ...settings, ...shorthand });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,20 +7,42 @@ import type { ShorthandResolver } from '../../../src/init/variables/ShorthandRes
|
|||||||
import { joinFilePath } from '../../../src/util/PathUtil';
|
import { joinFilePath } from '../../../src/util/PathUtil';
|
||||||
import { flushPromises } from '../../util/Util';
|
import { flushPromises } from '../../util/Util';
|
||||||
|
|
||||||
const defaultParameters = {
|
let defaultParameters: Record<string, any> = {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
};
|
};
|
||||||
|
|
||||||
const cliExtractor: jest.Mocked<CliExtractor> = {
|
const cliExtractor: jest.Mocked<CliExtractor> = {
|
||||||
handleSafe: jest.fn().mockResolvedValue(defaultParameters),
|
handleSafe: jest.fn((): Record<string, any> => defaultParameters),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const defaultVariables = {
|
let defaultVariables: Record<string, any> = {
|
||||||
'urn:solid-server:default:variable:port': 3000,
|
'urn:solid-server:default:variable:port': 3000,
|
||||||
'urn:solid-server:default:variable:loggingLevel': 'info',
|
'urn:solid-server:default:variable:loggingLevel': 'info',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shorthandKeys: Record<string, string> = {
|
||||||
|
port: 'urn:solid-server:default:variable:port',
|
||||||
|
logLevel: 'urn:solid-server:default:variable:loggingLevel',
|
||||||
|
};
|
||||||
|
|
||||||
const shorthandResolver: jest.Mocked<ShorthandResolver> = {
|
const shorthandResolver: jest.Mocked<ShorthandResolver> = {
|
||||||
handleSafe: jest.fn().mockResolvedValue(defaultVariables),
|
handleSafe: jest.fn((args: Record<string, any>): Record<string, any> => {
|
||||||
|
const variables: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const key in args) {
|
||||||
|
if (key in shorthandKeys) {
|
||||||
|
variables[shorthandKeys[key]] = args[key];
|
||||||
|
|
||||||
|
// We ignore the default key as this is introduced by the way
|
||||||
|
// we are mocking the module
|
||||||
|
} else if (key !== 'default') {
|
||||||
|
throw new Error(`Unexpected key ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const mockLogger = {
|
const mockLogger = {
|
||||||
@ -74,11 +96,61 @@ jest.mock('componentsjs', (): any => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let files: Record<string, any> = {};
|
||||||
|
|
||||||
|
const alternateParameters = {
|
||||||
|
port: 3101,
|
||||||
|
logLevel: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
const packageJSONbase = {
|
||||||
|
name: 'test',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const packageJSON = {
|
||||||
|
...packageJSONbase,
|
||||||
|
config: {
|
||||||
|
'community-solid-server': alternateParameters,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('fs', (): Partial<Record<string, jest.Mock>> => ({
|
||||||
|
cwd: jest.fn((): string => __dirname),
|
||||||
|
existsSync: jest.fn((pth: string): boolean => typeof pth === 'string' && pth in files),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('fs-extra', (): Partial<Record<string, jest.Mock>> => ({
|
||||||
|
readJSON: jest.fn(async(pth: string): Promise<any> => files[pth]),
|
||||||
|
pathExists: jest.fn(async(pth: string): Promise<boolean> => typeof pth === 'string' && pth in files),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'/var/cwd/.community-solid-server.config.js',
|
||||||
|
(): any => alternateParameters,
|
||||||
|
{ virtual: true },
|
||||||
|
);
|
||||||
|
|
||||||
jest.spyOn(process, 'cwd').mockReturnValue('/var/cwd');
|
jest.spyOn(process, 'cwd').mockReturnValue('/var/cwd');
|
||||||
const write = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
const write = jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
||||||
const exit = jest.spyOn(process, 'exit').mockImplementation(jest.fn() as any);
|
const exit = jest.spyOn(process, 'exit').mockImplementation(jest.fn() as any);
|
||||||
|
|
||||||
describe('AppRunner', (): void => {
|
describe('AppRunner', (): void => {
|
||||||
|
beforeEach((): void => {
|
||||||
|
files = {};
|
||||||
|
|
||||||
|
defaultParameters = {
|
||||||
|
port: 3000,
|
||||||
|
logLevel: 'info',
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultVariables = {
|
||||||
|
'urn:solid-server:default:variable:port': 3000,
|
||||||
|
'urn:solid-server:default:variable:loggingLevel': 'info',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
afterEach((): void => {
|
afterEach((): void => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
@ -547,6 +619,100 @@ describe('AppRunner', (): void => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('runs with no parameters.', async(): Promise<void> => {
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs honouring package.json configuration.', async(): Promise<void> => {
|
||||||
|
files = { '/var/cwd/package.json': packageJSON };
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {
|
||||||
|
'urn:solid-server:default:variable:port': 3101,
|
||||||
|
'urn:solid-server:default:variable:loggingLevel': 'error',
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs honouring package.json configuration with empty config.', async(): Promise<void> => {
|
||||||
|
files = { '/var/cwd/package.json': packageJSONbase };
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs honouring .community-solid-server.config.json if package.json is present.', async(): Promise<void> => {
|
||||||
|
files = {
|
||||||
|
'/var/cwd/.community-solid-server.config.json': alternateParameters,
|
||||||
|
'/var/cwd/package.json': packageJSONbase,
|
||||||
|
};
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {
|
||||||
|
'urn:solid-server:default:variable:port': 3101,
|
||||||
|
'urn:solid-server:default:variable:loggingLevel': 'error',
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs honouring .community-solid-server.config.js if package.json is present.', async(): Promise<void> => {
|
||||||
|
files = {
|
||||||
|
'/var/cwd/.community-solid-server.config.js': alternateParameters,
|
||||||
|
'/var/cwd/package.json': packageJSONbase,
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {
|
||||||
|
'urn:solid-server:default:variable:port': 3101,
|
||||||
|
'urn:solid-server:default:variable:loggingLevel': 'error',
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs ignoring .community-solid-server.config.json if no package.json present.', async(): Promise<void> => {
|
||||||
|
files = { '/var/cwd/.community-solid-server.config.json': alternateParameters };
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs ignoring .community-solid-server.config.js if no package.json present.', async(): Promise<void> => {
|
||||||
|
files = {
|
||||||
|
'/var/cwd/.community-solid-server.config.js': `module.exports = ${JSON.stringify(alternateParameters)}`,
|
||||||
|
};
|
||||||
|
defaultParameters = {};
|
||||||
|
defaultVariables = {};
|
||||||
|
|
||||||
|
await expect(new AppRunner().runCli()).resolves.toBeUndefined();
|
||||||
|
expect(manager.instantiate).toHaveBeenNthCalledWith(
|
||||||
|
2, 'urn:solid-server:default:App', { variables: {}},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('throws an error if the server could not start.', async(): Promise<void> => {
|
it('throws an error if the server could not start.', async(): Promise<void> => {
|
||||||
app.start.mockRejectedValueOnce(new Error('Fatal'));
|
app.start.mockRejectedValueOnce(new Error('Fatal'));
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user