Merge branch 'main' into versions/6.0.0

# Conflicts:
#	src/init/ServerInitializer.ts
#	src/server/BaseHttpServerFactory.ts
#	src/server/HttpServerFactory.ts
#	src/server/WebSocketServerFactory.ts
#	test/unit/server/BaseHttpServerFactory.test.ts
This commit is contained in:
Joachim Van Herwegen 2023-02-01 10:13:04 +01:00
commit 7cc0e3fbcc
30 changed files with 972 additions and 653 deletions

View File

@ -42,7 +42,7 @@ jobs:
with: with:
node-version: 16.x node-version: 16.x
- name: Check out the project - name: Check out the project
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
with: with:
ref: ${{ inputs.branch || github.ref }} ref: ${{ inputs.branch || github.ref }}
- name: Install dependencies and run build scripts - name: Install dependencies and run build scripts

View File

@ -21,7 +21,7 @@ jobs:
tags: ${{ steps.meta-main.outputs.tags || steps.meta-version.outputs.tags }} tags: ${{ steps.meta-main.outputs.tags || steps.meta-version.outputs.tags }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
- if: startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main') - if: startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main')
name: Docker meta edge and version tag name: Docker meta edge and version tag
id: meta-main id: meta-main
@ -55,7 +55,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
@ -66,7 +66,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and export to docker - name: Build and export to docker
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
load: true load: true
@ -85,10 +85,10 @@ jobs:
done <<< "${{ needs.docker-meta.outputs.tags }}"; done <<< "${{ needs.docker-meta.outputs.tags }}";
- name: Build and push - name: Build and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
context: . context: .
push: true push: true
platforms: linux/amd64,linux/arm/v7 platforms: linux/amd64,linux/arm/v7,linux/arm/v8
tags: ${{ needs.docker-meta.outputs.tags }} tags: ${{ needs.docker-meta.outputs.tags }}
labels: ${{ needs.docker-meta.outputs.labels }} labels: ${{ needs.docker-meta.outputs.labels }}

View File

@ -34,12 +34,7 @@ jobs:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
typedocs-release:
# Release typedocs on version tag, but ignore pre-releases
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-')
uses: ./.github/workflows/typedocs.yml
mkdocs-release: mkdocs-release:
# Release mkdocs on version tag, but ignore pre-releases # Release documentation on version tag, but ignore pre-releases
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-')
uses: ./.github/workflows/mkdocs.yml uses: ./.github/workflows/mkdocs.yml

View File

@ -15,13 +15,13 @@ on:
jobs: jobs:
mkdocs-prep: mkdocs-prep:
# Runs the markdown linter to ensure we don't release faulty markdown. # Runs the markdown linter to ensure we don't release faulty markdown.
# Also gets the correct major version, wether the job is triggered by a version tag # Also gets the correct major version, whether the job is triggered by a version tag
# or a push to main to update the latest documentation. # or a push to main to update the latest documentation.
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
major: ${{ steps.tagged_version.outputs.major || steps.current_version.ouputs.major }} major: ${{ steps.tagged_version.outputs.major || steps.current_version.outputs.major }}
steps: steps:
- uses: actions/checkout@v3.1.0 - uses: actions/checkout@v3.3.0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16.x' node-version: '16.x'
@ -37,13 +37,13 @@ jobs:
id: current_version id: current_version
run: | run: |
VERSION=$(git show origin/main:package.json | jq -r .version | grep -Po '^(\d+)') VERSION=$(git show origin/main:package.json | jq -r .version | grep -Po '^(\d+)')
echo "::set-output name=major::$VERSION" echo "major=$VERSION" >> $GITHUB_OUTPUT
mkdocs: mkdocs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: mkdocs-prep needs: mkdocs-prep
steps: steps:
- uses: actions/checkout@v3.1.0 - uses: actions/checkout@v3.3.0
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: 3.x python-version: 3.x
@ -55,3 +55,24 @@ jobs:
- run: | - run: |
cd documentation && mike deploy --push --update-aliases \ cd documentation && mike deploy --push --update-aliases \
${{ needs.mkdocs-prep.outputs.major }}.x latest ${{ needs.mkdocs-prep.outputs.major }}.x latest
typedocs:
# Build typedocs and publish them to the GH page.
# `mike deploy` overwrites the entire folder for a version so these need to be (re)built afterwards.
needs: [mkdocs-prep, mkdocs]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/setup-node@v3
with:
node-version: '16.x'
- run: npm ci --ignore-scripts
- name: Generate typedocs
run: npm run typedocs
- name: Deploy typedocs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
destination_dir: ${{ needs.mkdocs-prep.outputs.major }}.x/docs

View File

@ -7,7 +7,7 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3.1.0 - uses: actions/checkout@v3.3.0
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16.x' node-version: '16.x'
@ -38,7 +38,7 @@ jobs:
- name: Ensure line endings are consistent - name: Ensure line endings are consistent
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
- name: Install dependencies and run build scripts - name: Install dependencies and run build scripts
run: npm ci run: npm ci
- name: Type-check tests - name: Type-check tests
@ -81,7 +81,7 @@ jobs:
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
- name: Install dependencies and run build scripts - name: Install dependencies and run build scripts
run: npm ci run: npm ci
- name: Run integration tests - name: Run integration tests
@ -105,7 +105,7 @@ jobs:
- name: Ensure line endings are consistent - name: Ensure line endings are consistent
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
- name: Install dependencies and run build scripts - name: Install dependencies and run build scripts
run: npm ci run: npm ci
- name: Run integration tests - name: Run integration tests
@ -127,7 +127,7 @@ jobs:
with: with:
node-version: '16.x' node-version: '16.x'
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
- name: Install dependencies and run build scripts - name: Install dependencies and run build scripts
run: npm ci run: npm ci
- name: Run deploy tests - name: Run deploy tests

View File

@ -10,7 +10,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v6 - uses: actions/stale@v7
with: with:
debug-only: true debug-only: true
stale-issue-label: 🏚️ abandoned stale-issue-label: 🏚️ abandoned

View File

@ -1,25 +0,0 @@
name: Typedocs
on:
workflow_call:
jobs:
typedocs:
# Build typedocs and publish them to the GH page
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/setup-node@v3
with:
node-version: '16.x'
- run: npm ci --ignore-scripts
- name: Generate typedocs
run: npm run typedocs
- name: Get tagged version
id: version
uses: battila7/get-version-action@v2
- name: Deploy typedocs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
destination_dir: ${{ steps.version.outputs.major }}.x/docs

File diff suppressed because it is too large Load Diff

View File

@ -115,7 +115,7 @@ testing applications in different setups,
or developing new parts for the server or developing new parts for the server
without needing to change its base code. without needing to change its base code.
### ⏱ Parameters ### ⏱ Parameters
An easy way to customize the server is An easy way to customize the server is
by passing parameters to the server command. by passing parameters to the server command.
@ -123,9 +123,10 @@ These parameters give you direct access
to some commonly used settings: to some commonly used settings:
| parameter name | default value | description | | parameter name | default value | description |
|------------------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| |-------------------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| `--port, -p` | `3000` | The TCP port on which the server should listen. | | `--port, -p` | `3000` | The TCP port on which the server should listen. |
| `--baseUrl, -b` | `http://localhost:$PORT/` | The base URL used internally to generate URLs. Change this if your server does not run on `http://localhost:$PORT/`. | | `--baseUrl, -b` | `http://localhost:$PORT/` | The base URL used internally to generate URLs. Change this if your server does not run on `http://localhost:$PORT/`. |
| `--socket` | | The Unix Domain Socket on which the server should listen. `--baseUrl` must be set if this option is provided |
| `--loggingLevel, -l` | `info` | The detail level of logging; useful for debugging problems. Use `debug` for full information. | | `--loggingLevel, -l` | `info` | The detail level of logging; useful for debugging problems. Use `debug` for full information. |
| `--config, -c` | `@css:config/default.json` | The configuration(s) for the server. The default only stores data in memory; to persist to your filesystem, use `@css:config/file.json` | | `--config, -c` | `@css:config/default.json` | The configuration(s) for the server. The default only stores data in memory; to persist to your filesystem, use `@css:config/file.json` |
| `--rootFilePath, -f` | `./` | Root folder where the server stores data, when using a file-based configuration. | | `--rootFilePath, -f` | `./` | Root folder where the server stores data, when using a file-based configuration. |
@ -142,7 +143,7 @@ The Community Solid Server can be started in multithreaded mode with any config.
that are threadsafe though. If a non-threadsafe component is used in multithreaded mode, the server will describe with that are threadsafe though. If a non-threadsafe component is used in multithreaded mode, the server will describe with
an error which class is the culprit. an error which class is the culprit.
```node ```shell
# Running multithreaded with autoscaling to number of logical cores minus 1 # Running multithreaded with autoscaling to number of logical cores minus 1
npm start -- -c config/file.json -w -1 npm start -- -c config/file.json -w -1
``` ```
@ -174,13 +175,10 @@ Recipes for configuring the server can be found at [CommunitySolidServer/recipes
The server allows writing and plugging in custom modules The server allows writing and plugging in custom modules
without altering its base source code. without altering its base source code.
The [📗 API documentation](https://communitysolidserver.github.io/CommunitySolidServer/latest/docs) and The [📗 API documentation](https://communitysolidserver.github.io/CommunitySolidServer/latest/5.x/docs) and
the [📐 architectural diagram](https://rubenverborgh.github.io/solid-server-architecture/solid-architecture-v1-3-0.pdf) the [📓 user documentation](https://communitysolidserver.github.io/CommunitySolidServer/)
can help you find your way. can help you find your way.
There is also a repository of [📚 comprehensive tutorials](https://github.com/CommunitySolidServer/tutorials/)
If you want to help out with server development,
have a look at the [📓 user documentation](https://communitysolidserver.github.io/CommunitySolidServer/) and
[🛠 good first issues](https://github.com/CommunitySolidServer/CommunitySolidServer/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
## 📜 License ## 📜 License
@ -189,13 +187,6 @@ is copyrighted by [Inrupt Inc.](https://inrupt.com/)
and [imec](https://www.imec-int.com/) and [imec](https://www.imec-int.com/)
and available under the [MIT License](https://github.com/CommunitySolidServer/CommunitySolidServer/blob/main/LICENSE.md). and available under the [MIT License](https://github.com/CommunitySolidServer/CommunitySolidServer/blob/main/LICENSE.md).
Core contributors are
[Joachim Van Herwegen](https://github.com/joachimvh),
[Ruben Verborgh](https://github.com/RubenVerborgh),
[Ruben Taelman](https://github.com/rubensworks),
and
[Matthieu Bosquet](https://github.com/matthieubosquet).
## 🎤 Feedback and questions ## 🎤 Feedback and questions
Don't hesitate to [start a discussion](https://github.com/CommunitySolidServer/CommunitySolidServer/discussions) Don't hesitate to [start a discussion](https://github.com/CommunitySolidServer/CommunitySolidServer/discussions)

View File

@ -96,6 +96,7 @@ These changes are relevant if you wrote custom modules for the server that depen
- 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

5
SECURITY.md Normal file
View File

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
To report and discuss security vulnerabilities go to <https://github.com/CommunitySolidServer/CommunitySolidServer/security/advisories>

View File

@ -1,3 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
const { AppRunner } = require('..'); const { AppRunner } = require('..');
// Attaching a logger to the uncaughtExceptionMonitor event,
// such that the default uncaughtException behavior still occurs.
process.on('uncaughtExceptionMonitor', (err, origin) => {
console.error(`Process is halting due to an ${origin} with error ${err.message}`);
});
new AppRunner().runCliSync(process); new AppRunner().runCliSync(process);

View File

@ -6,7 +6,8 @@
"@id": "urn:solid-server:default:ServerInitializer", "@id": "urn:solid-server:default:ServerInitializer",
"@type": "ServerInitializer", "@type": "ServerInitializer",
"serverFactory": { "@id": "urn:solid-server:default:ServerFactory" }, "serverFactory": { "@id": "urn:solid-server:default:ServerFactory" },
"port": { "@id": "urn:solid-server:default:variable:port" } "port": { "@id": "urn:solid-server:default:variable:port" },
"socketPath": { "@id": "urn:solid-server:default:variable:socket" }
} }
] ]
} }

View File

@ -56,6 +56,15 @@
"describe": "The TCP port on which the server runs." "describe": "The TCP port on which the server runs."
} }
}, },
{
"@type": "YargsParameter",
"name": "socket",
"options": {
"requiresArg": true,
"type": "string",
"describe": "The path to the Unix Domain Socket on which the server runs. This overrides the port argument."
}
},
{ {
"@type": "YargsParameter", "@type": "YargsParameter",
"name": "rootFilePath", "name": "rootFilePath",

View File

@ -28,6 +28,14 @@
"defaultValue": 3000 "defaultValue": 3000
} }
}, },
{
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:socket",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "socket",
"defaultValue" : ""
}
},
{ {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:rootFilePath", "CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:rootFilePath",
"CombinedShorthandResolver:_resolvers_value": { "CombinedShorthandResolver:_resolvers_value": {

View File

@ -23,6 +23,7 @@
"openid": [ "azp" ], "openid": [ "azp" ],
"webid": [ "webid" ] "webid": [ "webid" ]
}, },
"clockTolerance": 120,
"cookies": { "cookies": {
"long": { "signed": true, "maxAge": 86400000 }, "long": { "signed": true, "maxAge": 86400000 },
"short": { "signed": true } "short": { "signed": true }

View File

@ -7,6 +7,11 @@
"@id": "urn:solid-server:default:variable:port", "@id": "urn:solid-server:default:variable:port",
"@type": "Variable" "@type": "Variable"
}, },
{
"comment": "Unix Domain Socket of the server.",
"@id": "urn:solid-server:default:variable:socket",
"@type": "Variable"
},
{ {
"comment": "Needs to be set to the base URL of the server for authentication and authorization to function.", "comment": "Needs to be set to the base URL of the server for authentication and authorization to function.",
"@id": "urn:solid-server:default:variable:baseUrl", "@id": "urn:solid-server:default:variable:baseUrl",

View File

@ -34,12 +34,17 @@ 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
* [How the server uses dependency injection](architecture/dependency-injection.md) * [How the server uses dependency injection](architecture/dependency-injection.md)
* [What the architecture looks like](architecture/overview.md) * [What the architecture looks like](architecture/overview.md)
## Comprehensive guides and tutorials
* [The CSS tutorial repository](https://github.com/CommunitySolidServer/tutorials/)
## Making changes ## Making changes
* [How to make changes to the repository](contributing/making-changes.md) * [How to make changes to the repository](contributing/making-changes.md)

View 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"
};
```

122
package-lock.json generated
View File

@ -38,7 +38,7 @@
"arrayify-stream": "^2.0.0", "arrayify-stream": "^2.0.0",
"async-lock": "^1.3.2", "async-lock": "^1.3.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"componentsjs": "^5.3.0", "componentsjs": "^5.3.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"ejs": "^3.1.8", "ejs": "^3.1.8",
@ -6136,9 +6136,9 @@
"dev": true "dev": true
}, },
"node_modules/componentsjs": { "node_modules/componentsjs": {
"version": "5.3.0", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-5.3.0.tgz", "resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-5.3.2.tgz",
"integrity": "sha512-eteUmYCezs4a/ZBVxk3graGPlNNrdilSrmsWOSWOYO41KTqe054Q5zSdpetNNchhOUwPbIcdP29JuuBdAd2/fQ==", "integrity": "sha512-wqXaHjrnT4UDQT8Eaou/Itd55OWE7wasBivPJ0qfSlRMi5zRAwp3+sEgGO7F5T7hs0rMsrGTnkWWcoSHmrM/8A==",
"dependencies": { "dependencies": {
"@rdfjs/types": "*", "@rdfjs/types": "*",
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
@ -6702,9 +6702,9 @@
"dev": true "dev": true
}, },
"node_modules/cookiejar": { "node_modules/cookiejar": {
"version": "2.1.3", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true "dev": true
}, },
"node_modules/cookies": { "node_modules/cookies": {
@ -7143,9 +7143,9 @@
} }
}, },
"node_modules/dezalgo": { "node_modules/dezalgo": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"asap": "^2.0.0", "asap": "^2.0.0",
@ -8782,32 +8782,20 @@
} }
}, },
"node_modules/formidable": { "node_modules/formidable": {
"version": "2.0.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"dezalgo": "1.0.3", "dezalgo": "^1.0.4",
"hexoid": "1.0.0", "hexoid": "^1.0.0",
"once": "1.4.0", "once": "^1.4.0",
"qs": "6.9.3" "qs": "^6.11.0"
}, },
"funding": { "funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions" "url": "https://ko-fi.com/tunnckoCore/commissions"
} }
}, },
"node_modules/formidable/node_modules/qs": {
"version": "6.9.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
"dev": true,
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fresh": { "node_modules/fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -11077,9 +11065,9 @@
"dev": true "dev": true
}, },
"node_modules/json5": { "node_modules/json5": {
"version": "2.2.1", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true, "dev": true,
"bin": { "bin": {
"json5": "lib/cli.js" "json5": "lib/cli.js"
@ -13718,9 +13706,9 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
}, },
"node_modules/simple-git": { "node_modules/simple-git": {
"version": "3.12.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.12.0.tgz", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.16.0.tgz",
"integrity": "sha512-cy1RSRFHGZSrlYa3MnUuNVOXLUdifEZD2X8+AZjg8mKCdRvtCFSga6acq5N2g0ggb8lH3jBi369MrFZ+Y6sfsA==", "integrity": "sha512-zuWYsOLEhbJRWVxpjdiXl6eyAyGo/KzVW+KFhhw9MqEEJttcq+32jTWSGyxTdf9e/YCohxRE+9xpWFj9FdiJNw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@kwsites/file-exists": "^1.1.1", "@kwsites/file-exists": "^1.1.1",
@ -14592,9 +14580,9 @@
} }
}, },
"node_modules/tsconfig-paths/node_modules/json5": { "node_modules/tsconfig-paths/node_modules/json5": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.0" "minimist": "^1.2.0"
@ -20402,9 +20390,9 @@
"dev": true "dev": true
}, },
"componentsjs": { "componentsjs": {
"version": "5.3.0", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-5.3.0.tgz", "resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-5.3.2.tgz",
"integrity": "sha512-eteUmYCezs4a/ZBVxk3graGPlNNrdilSrmsWOSWOYO41KTqe054Q5zSdpetNNchhOUwPbIcdP29JuuBdAd2/fQ==", "integrity": "sha512-wqXaHjrnT4UDQT8Eaou/Itd55OWE7wasBivPJ0qfSlRMi5zRAwp3+sEgGO7F5T7hs0rMsrGTnkWWcoSHmrM/8A==",
"requires": { "requires": {
"@rdfjs/types": "*", "@rdfjs/types": "*",
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
@ -20854,9 +20842,9 @@
} }
}, },
"cookiejar": { "cookiejar": {
"version": "2.1.3", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true "dev": true
}, },
"cookies": { "cookies": {
@ -21184,9 +21172,9 @@
"dev": true "dev": true
}, },
"dezalgo": { "dezalgo": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true, "dev": true,
"requires": { "requires": {
"asap": "^2.0.0", "asap": "^2.0.0",
@ -22426,23 +22414,15 @@
} }
}, },
"formidable": { "formidable": {
"version": "2.0.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"dezalgo": "1.0.3", "dezalgo": "^1.0.4",
"hexoid": "1.0.0", "hexoid": "^1.0.0",
"once": "1.4.0", "once": "^1.4.0",
"qs": "6.9.3" "qs": "^6.11.0"
},
"dependencies": {
"qs": {
"version": "6.9.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
"dev": true
}
} }
}, },
"fresh": { "fresh": {
@ -24134,9 +24114,9 @@
"dev": true "dev": true
}, },
"json5": { "json5": {
"version": "2.2.1", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true "dev": true
}, },
"jsonc-parser": { "jsonc-parser": {
@ -26166,9 +26146,9 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
}, },
"simple-git": { "simple-git": {
"version": "3.12.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.12.0.tgz", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.16.0.tgz",
"integrity": "sha512-cy1RSRFHGZSrlYa3MnUuNVOXLUdifEZD2X8+AZjg8mKCdRvtCFSga6acq5N2g0ggb8lH3jBi369MrFZ+Y6sfsA==", "integrity": "sha512-zuWYsOLEhbJRWVxpjdiXl6eyAyGo/KzVW+KFhhw9MqEEJttcq+32jTWSGyxTdf9e/YCohxRE+9xpWFj9FdiJNw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@kwsites/file-exists": "^1.1.1", "@kwsites/file-exists": "^1.1.1",
@ -26853,9 +26833,9 @@
}, },
"dependencies": { "dependencies": {
"json5": { "json5": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true, "dev": true,
"requires": { "requires": {
"minimist": "^1.2.0" "minimist": "^1.2.0"

View File

@ -83,7 +83,7 @@
"commit-and-tag-version": { "commit-and-tag-version": {
"scripts": { "scripts": {
"postbump": "ts-node ./scripts/upgradeConfig.ts", "postbump": "ts-node ./scripts/upgradeConfig.ts",
"postchangelog": "ts-node ./scripts/formatChangelog.ts" "postchangelog": "ts-node ./scripts/formatChangelog.ts && markdownlint-cli2-fix"
}, },
"writerOpts": { "writerOpts": {
"commitsSort": false "commitsSort": false
@ -128,7 +128,7 @@
"arrayify-stream": "^2.0.0", "arrayify-stream": "^2.0.0",
"async-lock": "^1.3.2", "async-lock": "^1.3.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"componentsjs": "^5.3.0", "componentsjs": "^5.3.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"ejs": "^3.1.8", "ejs": "^3.1.8",

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ts-node #!/usr/bin/env ts-node
/* eslint-disable no-console */ /* eslint-disable no-console */
import { readFile, writeFile } from 'fs-extra'; import { readFile, writeFile } from 'fs-extra';
@ -8,18 +9,16 @@ import { readFile, writeFile } from 'fs-extra';
* to the changelog. * to the changelog.
* Current automatic changes: * Current automatic changes:
* - Change all version titles to H2 ("### [vX.Y.Z]" to "## [vX.Y.Z]") * - Change all version titles to H2 ("### [vX.Y.Z]" to "## [vX.Y.Z]")
* - Capitalize all list entries
*/ */
/** /**
* @param from - Regular expression to search for * Capitalize all list entries
* @param to - String to replace to * @param input - String to search/replace
* @param filePath - File to search/replace * @returns Promise with output string
* @returns Promise
*/ */
async function replaceInFile(from: RegExp, to: string, filePath: string): Promise<void> { async function capitalizeListEntries(input: string): Promise<string> {
const data = await readFile(filePath, 'utf8'); return input.replace(/^(\W*\* [a-z])/gmu, (match): string => match.toUpperCase());
const result = data.replace(from, to);
return writeFile(filePath, result, 'utf8');
} }
/** /**
@ -30,4 +29,15 @@ function endProcess(error: Error): never {
process.exit(1); process.exit(1);
} }
replaceInFile(/### \[/gu, '## [', 'CHANGELOG.md').catch(endProcess); /**
* Main function for changelog formatting
* @param filePath - Path to the changelog file
* @returns Promise
*/
async function formatChangelog(filePath: string): Promise<void> {
let changelog = await readFile(filePath, 'utf8');
changelog = await capitalizeListEntries(changelog);
return writeFile(filePath, changelog, 'utf8');
}
formatChangelog('CHANGELOG.md').catch(endProcess);

View File

@ -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 });
} }
/** /**

View File

@ -14,23 +14,33 @@ export class ServerInitializer extends Initializer implements Finalizable {
protected readonly logger = getLoggerFor(this); protected readonly logger = getLoggerFor(this);
private readonly serverFactory: HttpServerFactory; private readonly serverFactory: HttpServerFactory;
private readonly port: number; private readonly port?: number;
private readonly socketPath?: string;
private server?: Server; private server?: Server;
public constructor(serverFactory: HttpServerFactory, port: number) { public constructor(serverFactory: HttpServerFactory, port?: number, socketPath?: string) {
super(); super();
this.serverFactory = serverFactory; this.serverFactory = serverFactory;
this.port = port; this.port = port;
this.socketPath = socketPath;
if (!port && !socketPath) {
throw new Error('Either Port or Socket arguments must be set');
}
} }
public async handle(): Promise<void> { public async handle(): Promise<void> {
this.server = await this.serverFactory.createServer(); this.server = await this.serverFactory.createServer();
if (this.socketPath) {
this.logger.info(`Listening to server at ${this.server.address()}`);
this.server.listen(this.socketPath);
} else {
const url = new URL(`http${isHttpsServer(this.server) ? 's' : ''}://localhost:${this.port}/`).href; const url = new URL(`http${isHttpsServer(this.server) ? 's' : ''}://localhost:${this.port}/`).href;
this.logger.info(`Listening to server at ${url}`); this.logger.info(`Listening to server at ${url}`);
this.server.listen(this.port); this.server.listen(this.port);
} }
}
public async finalize(): Promise<void> { public async finalize(): Promise<void> {
if (this.server) { if (this.server) {

View File

@ -18,6 +18,9 @@ export class BaseUrlExtractor extends ShorthandExtractor {
if (typeof args.baseUrl === 'string') { if (typeof args.baseUrl === 'string') {
return ensureTrailingSlash(args.baseUrl); return ensureTrailingSlash(args.baseUrl);
} }
if (typeof args.socket === 'string') {
throw new Error('BaseUrl argument should be provided when using Unix Domain Sockets.');
}
const port = args.port ?? this.defaultPort; const port = args.port ?? this.defaultPort;
return `http://localhost:${port}/`; return `http://localhost:${port}/`;
} }

View File

@ -51,6 +51,7 @@ export function getDefaultVariables(port: number, baseUrl?: string): Record<stri
return { return {
'urn:solid-server:default:variable:baseUrl': baseUrl ?? `http://localhost:${port}/`, 'urn:solid-server:default:variable:baseUrl': baseUrl ?? `http://localhost:${port}/`,
'urn:solid-server:default:variable:port': port, 'urn:solid-server:default:variable:port': port,
'urn:solid-server:default:variable:socket': null,
'urn:solid-server:default:variable:loggingLevel': 'off', 'urn:solid-server:default:variable:loggingLevel': 'off',
'urn:solid-server:default:variable:showStackTrace': true, 'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:seededPodConfigJson': null, 'urn:solid-server:default:variable:seededPodConfigJson': null,

View File

@ -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'));

View File

@ -20,6 +20,7 @@ describe('ServerInitializer', (): void => {
(getLoggerFor as jest.MockedFn<() => Logger>).mockReturnValue(logger); (getLoggerFor as jest.MockedFn<() => Logger>).mockReturnValue(logger);
server = { server = {
address: jest.fn().mockResolvedValue('address'),
listen: jest.fn(), listen: jest.fn(),
close: jest.fn((fn: () => void): void => fn()), close: jest.fn((fn: () => void): void => fn()),
} as any; } as any;
@ -49,6 +50,18 @@ describe('ServerInitializer', (): void => {
expect(logger.info).toHaveBeenLastCalledWith(`Listening to server at https://localhost:3000/`); expect(logger.info).toHaveBeenLastCalledWith(`Listening to server at https://localhost:3000/`);
}); });
it('listens to the specified Unix Domain Socket.', async(): Promise<void> => {
initializer = new ServerInitializer(serverFactory, undefined, '/tmp/css.sock');
await initializer.handle();
expect(server.listen).toHaveBeenCalledWith('/tmp/css.sock');
});
it('throws when neither port or socket are set.', async(): Promise<void> => {
expect((): void => {
initializer = new ServerInitializer(serverFactory, undefined, undefined);
}).toThrow('Either Port or Socket arguments must be set');
});
it('can stop the server.', async(): Promise<void> => { it('can stop the server.', async(): Promise<void> => {
await initializer.handle(); await initializer.handle();
await expect(initializer.finalize()).resolves.toBeUndefined(); await expect(initializer.finalize()).resolves.toBeUndefined();

View File

@ -16,6 +16,11 @@ describe('A BaseUrlExtractor', (): void => {
await expect(computer.handle({ port: 3333 })).resolves.toBe('http://localhost:3333/'); await expect(computer.handle({ port: 3333 })).resolves.toBe('http://localhost:3333/');
}); });
it('throws when a Unix Socket Path is provided without a baseUrl.', async(): Promise<void> => {
await expect(computer.handle({ socket: '/tmp/css.sock' })).rejects
.toThrow('BaseUrl argument should be provided when using Unix Domain Sockets.');
});
it('defaults to port 3000.', async(): Promise<void> => { it('defaults to port 3000.', async(): Promise<void> => {
await expect(computer.handle({})).resolves.toBe('http://localhost:3000/'); await expect(computer.handle({})).resolves.toBe('http://localhost:3000/');
}); });

View File

@ -37,6 +37,11 @@ const portNames = [
'BaseServerFactory', 'BaseServerFactory',
] as const; ] as const;
const socketNames = [
// Unit
'BaseHttpServerFactory',
];
export function getPort(name: typeof portNames[number]): number { export function getPort(name: typeof portNames[number]): number {
const idx = portNames.indexOf(name); const idx = portNames.indexOf(name);
// Just in case something doesn't listen to the typings // Just in case something doesn't listen to the typings
@ -46,6 +51,15 @@ export function getPort(name: typeof portNames[number]): number {
return 6000 + idx; return 6000 + idx;
} }
export function getSocket(name: typeof socketNames[number]): string {
const idx = socketNames.indexOf(name);
// Just in case something doesn't listen to the typings
if (idx < 0) {
throw new Error(`Unknown socket name ${name}`);
}
return `css${idx}.sock`;
}
export function describeIf(envFlag: string): Describe { export function describeIf(envFlag: string): Describe {
const flag = `TEST_${envFlag.toUpperCase()}`; const flag = `TEST_${envFlag.toUpperCase()}`;
const enabled = !/^(|0|false)$/iu.test(process.env[flag] ?? ''); const enabled = !/^(|0|false)$/iu.test(process.env[flag] ?? '');