feat(node): move to node 20 (#4594)
This commit is contained in:
parent
ab41183ce5
commit
1f04d6c1be
@ -6,7 +6,7 @@ orbs:
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: cimg/node:18.18
|
||||
- image: cimg/node:20.18.1
|
||||
environment:
|
||||
TERM: xterm
|
||||
QUICK_BUILD: true
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -64,7 +64,7 @@ body:
|
||||
attributes:
|
||||
label: 'Node version'
|
||||
description: 'Your Node.js version.'
|
||||
placeholder: 'e.g., 18.16.1'
|
||||
placeholder: 'e.g., 20.18.1'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
||||
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Set up Git safe directory
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Set up Git safe directory
|
||||
|
||||
@ -1 +1 @@
|
||||
18.16.1
|
||||
20.9.0
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
# Stage 1: Build the application
|
||||
# docker build -t ohif/viewer:latest .
|
||||
FROM node:18.16.1-slim as json-copier
|
||||
FROM node:20.18.1-slim as json-copier
|
||||
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
@ -37,7 +37,7 @@ COPY platform /usr/src/app/platform
|
||||
#RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf
|
||||
|
||||
# Copy Files
|
||||
FROM node:18.16.1-slim as builder
|
||||
FROM node:20.18.1-slim as builder
|
||||
RUN apt-get update && apt-get install -y build-essential python3
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^2.7.3",
|
||||
"@cornerstonejs/core": "^2.7.3",
|
||||
"@cornerstonejs/adapters": "^2.11.2",
|
||||
"@cornerstonejs/core": "^2.11.2",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^2.7.3",
|
||||
"@cornerstonejs/core": "^2.7.3",
|
||||
"@cornerstonejs/adapters": "^2.11.2",
|
||||
"@cornerstonejs/core": "^2.11.2",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^2.7.3",
|
||||
"@cornerstonejs/core": "^2.7.3",
|
||||
"@cornerstonejs/tools": "^2.7.3",
|
||||
"@cornerstonejs/adapters": "^2.11.2",
|
||||
"@cornerstonejs/core": "^2.11.2",
|
||||
"@cornerstonejs/tools": "^2.11.2",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^2.7.3",
|
||||
"@cornerstonejs/tools": "^2.7.3",
|
||||
"@cornerstonejs/core": "^2.11.2",
|
||||
"@cornerstonejs/tools": "^2.11.2",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.7.3",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.11.2",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@ohif/core": "3.10.0-beta.20",
|
||||
"@ohif/ui": "3.10.0-beta.20",
|
||||
@ -55,9 +55,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^2.7.3",
|
||||
"@cornerstonejs/core": "^2.7.3",
|
||||
"@cornerstonejs/tools": "^2.7.3",
|
||||
"@cornerstonejs/adapters": "^2.11.2",
|
||||
"@cornerstonejs/core": "^2.11.2",
|
||||
"@cornerstonejs/tools": "^2.11.2",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@ -171,51 +171,61 @@ export default function PanelSegmentation({
|
||||
onSegmentationDownload,
|
||||
onSegmentationDownloadRTSS,
|
||||
t,
|
||||
}) => (
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => onSegmentationAdd(activeSegmentation.id)}>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">{t('Create New Segmentation')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>{t('Manage Current Segmentation')}</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => onSegmentationRemoveFromViewport(activeSegmentation.id)}>
|
||||
<Icons.Series className="text-foreground" />
|
||||
<span className="pl-2">{t('Remove from Viewport')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onSegmentationEdit(activeSegmentation.id)}>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">{t('Rename')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger
|
||||
disabled={!allowExport}
|
||||
className="pl-1"
|
||||
}) => {
|
||||
return (
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => onSegmentationAdd(activeSegmentation.segmentationId)}>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">{t('Create New Segmentation')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>{t('Manage Current Segmentation')}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onSegmentationRemoveFromViewport(activeSegmentation.segmentationId)}
|
||||
>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">{t('Export & Download')}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => storeSegmentation(activeSegmentation.id)}>
|
||||
{t('Export DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onSegmentationDownload(activeSegmentation.id)}>
|
||||
{t('Download DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onSegmentationDownloadRTSS(activeSegmentation.id)}>
|
||||
{t('Download DICOM RTSTRUCT')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => onSegmentationDelete(activeSegmentation.id)}>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
<span className="pl-2 text-red-600">{t('Delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
)
|
||||
<Icons.Series className="text-foreground" />
|
||||
<span className="pl-2">{t('Remove from Viewport')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onSegmentationEdit(activeSegmentation.segmentationId)}>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">{t('Rename')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger
|
||||
disabled={!allowExport}
|
||||
className="pl-1"
|
||||
>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">{t('Export & Download')}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem
|
||||
onClick={() => storeSegmentation(activeSegmentation.segmentationId)}
|
||||
>
|
||||
{t('Export DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onSegmentationDownload(activeSegmentation.segmentationId)}
|
||||
>
|
||||
{t('Download DICOM SEG')}
|
||||
</DropdownMenuItem>
|
||||
{/* <DropdownMenuItem
|
||||
onClick={() => onSegmentationDownloadRTSS(activeSegmentation.segmentationId)}
|
||||
>
|
||||
{t('Download DICOM RTSTRUCT')}
|
||||
</DropdownMenuItem> */}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => onSegmentationDelete(activeSegmentation.segmentationId)}>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
<span className="pl-2 text-red-600">{t('Delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => {
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^2.7.3",
|
||||
"@cornerstonejs/tools": "^2.7.3",
|
||||
"@cornerstonejs/core": "^2.11.2",
|
||||
"@cornerstonejs/tools": "^2.11.2",
|
||||
"@ohif/core": "3.10.0-beta.20",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0-beta.20",
|
||||
"@ohif/extension-default": "3.10.0-beta.20",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build the application
|
||||
FROM node:18.16.1-slim as builder
|
||||
FROM node:20.18.1-slim as builder
|
||||
|
||||
# Setup the working directory
|
||||
RUN mkdir /usr/src/app
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build the application
|
||||
FROM node:18.16.1-slim as builder
|
||||
FROM node:20.18.1-slim as builder
|
||||
|
||||
# Setup the working directory
|
||||
RUN mkdir /usr/src/app
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build the application
|
||||
FROM node:18.16.1-slim as builder
|
||||
FROM node:20.18.1-slim as builder
|
||||
|
||||
# Setup the working directory
|
||||
RUN mkdir /usr/src/app
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build the application
|
||||
FROM node:18.16.1-slim as builder
|
||||
FROM node:20.18.1-slim as builder
|
||||
|
||||
# Setup the working directory
|
||||
RUN mkdir /usr/src/app
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
[build.environment]
|
||||
# If 'production', `yarn install` does not install devDependencies
|
||||
NODE_ENV = "development"
|
||||
NODE_VERSION = "18.16.1"
|
||||
NODE_VERSION = "20.18.1"
|
||||
YARN_VERSION = "1.22.5"
|
||||
RUBY_VERSION = "2.6.2"
|
||||
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.7.3",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.11.2",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.10.0-beta.20",
|
||||
"@ohif/extension-cornerstone": "3.10.0-beta.20",
|
||||
|
||||
@ -12,41 +12,16 @@ window.config = {
|
||||
showLoadingIndicator: true,
|
||||
strictZSpacingForVolumeViewport: true,
|
||||
// filterQueryParam: false,
|
||||
defaultDataSourceName: 'dicomweb',
|
||||
defaultDataSourceName: 'local',
|
||||
dataSources: [
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'e2e',
|
||||
configuration: {
|
||||
friendlyName: 'StaticWado test data',
|
||||
// The most important field to set for static WADO
|
||||
staticWado: true,
|
||||
name: 'StaticWADO',
|
||||
wadoUriRoot: '/viewer-testdata',
|
||||
qidoRoot: '/viewer-testdata',
|
||||
wadoRoot: '/viewer-testdata',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
singlepart: 'video,thumbnail,pdf',
|
||||
omitQuotationForMultipartRequest: true,
|
||||
bulkDataURI: {
|
||||
enabled: true,
|
||||
relativeResolution: 'studies',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'local',
|
||||
configuration: {
|
||||
friendlyName: 'Static WADO Local Data',
|
||||
name: 'DCM4CHEE',
|
||||
qidoRoot: 'http://localhost:5000/dicomweb',
|
||||
wadoRoot: 'http://localhost:5000/dicomweb',
|
||||
qidoRoot: 'http://localhost:3001/dicomweb',
|
||||
wadoRoot: 'http://localhost:3001/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: true,
|
||||
supportsStow: true,
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.7.3",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.11.2",
|
||||
"@ohif/ui": "3.10.0-beta.20",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21"
|
||||
|
||||
BIN
platform/docs/docs/assets/img/static-dicom-web.png
Normal file
BIN
platform/docs/docs/assets/img/static-dicom-web.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@ -3,45 +3,98 @@ sidebar_position: 5
|
||||
sidebar_label: Static Files
|
||||
---
|
||||
|
||||
# Static Files
|
||||
|
||||
There is a binary DICOM to static file generator, which provides easily served
|
||||
binary files. The files are all compressed in order to reduce space
|
||||
significantly, and are pre-computed for the files required for OHIF, so that the
|
||||
performance of serving the files is just the read from disk/write to http stream
|
||||
time, without any extra processing time.
|
||||
# Static DICOMweb Files for Enhanced Performance
|
||||
|
||||
The project for the static wado files is located here: [static-wado]:
|
||||
https://github.com/OHIF/static-wado
|
||||
This section describes how to generate and serve static DICOMweb files, significantly improving the performance of your OHIF Viewer setup. These files are pre-processed and compressed, minimizing storage space and reducing serving time to the bare minimum (disk read and HTTP stream write).
|
||||
|
||||
It can be compiled with Java and Gradle, and then run against a set of dicom, in
|
||||
the example located in /dicom/study1 outputting to /dicomweb, and then a server
|
||||
run against that data, like this:
|
||||
## Static-DICOMWeb Project
|
||||
|
||||
```bash
|
||||
git clone https://github.com/OHIF/static-wado
|
||||
cd static-wado
|
||||
./gradlew installDist
|
||||
StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1
|
||||
cd /dicomweb
|
||||
npx http-server -p 5000 --cors -g
|
||||
The core tool for this process is the `static-wado` project, available on GitHub:
|
||||
|
||||
# you can use npx serve ./dist -l 8080 -c ../public/serve.json as an alternative to http-server
|
||||
```
|
||||
[static-wado]: https://github.com/RadicalImaging/Static-DICOMWeb
|
||||
|
||||
There is then a dev environment in the platform/app directory which can be
|
||||
run against those files, like this:
|
||||
This project contains two main components:
|
||||
|
||||
```
|
||||
cd platform/app
|
||||
yarn dev:static
|
||||
```
|
||||
* **`static-wado-creator`:** Converts raw DICOM files into a DICOMweb-compliant directory structure, optimizing them for efficient serving.
|
||||
* **`static-wado-webserver`:** A simple web server specifically designed to serve the generated static DICOMweb files.
|
||||
|
||||
Additional studies can be added to the dicomweb by re-running the StaticWado
|
||||
command. It will create a single studies.gz index file (JSON DICOM file,
|
||||
compressed) containing an index of all studies created. There is then a small
|
||||
extension to OHIF which performs client side indexing.
|
||||
## Prerequisites
|
||||
|
||||
The StaticWado command also knows how to deploy a client and dicomweb directory
|
||||
to Amazon s3, which can then server files up directly. There is another build
|
||||
setup build:aws in the viewer package.json to create such a deployment.
|
||||
- Node.js and npm (or yarn) installed on your system.
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Clone the Repository:**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/RadicalImaging/Static-DICOMWeb
|
||||
cd Static-DICOMWeb
|
||||
```
|
||||
|
||||
2. **Install Dependencies:**
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Generating Static DICOMweb Files
|
||||
|
||||
1. **Prepare your DICOM data:** Organize your DICOM files into a directory. For example `/Users/alireza/dicom/test-static-script/ACRIN-CT`.
|
||||
|
||||
2. **Convert to DICOMweb Structure:**
|
||||
Use the `mkdicomweb.js` script from the `static-wado-creator` package to create the DICOMweb directory:
|
||||
|
||||
```bash
|
||||
node packages/static-wado-creator/bin/mkdicomweb.js '/Users/alireza/dicom/test-static-script/ACRIN-CT' -o '/Users/alireza/dicom/test-static-script/output'
|
||||
```
|
||||
|
||||
* **Replace:**
|
||||
* `/Users/alireza/dicom/test-static-script/ACRIN-CT` with the path to your directory of DICOM files.
|
||||
* `/Users/alireza/dicom/test-static-script/output` with your desired output directory for the DICOMweb structure.
|
||||
|
||||
This command will generate a directory structure similar to this in your output location:
|
||||
|
||||

|
||||
|
||||
## Serving Static Files with the Web Server
|
||||
|
||||
1. **Start the Server:**
|
||||
|
||||
Run the `dicomwebserver.mjs` script from the `static-wado-webserver` package, specifying the port and the DICOMweb directory:
|
||||
|
||||
```bash
|
||||
node packages/static-wado-webserver/bin/dicomwebserver.mjs -p 3001 -o /Users/alireza/dicom/test-static-script/output
|
||||
```
|
||||
|
||||
* **`-p 3001`:** Sets the server to listen on port 3001. You can change this if needed.
|
||||
* **`-o /Users/alireza/dicom/test-static-script/output`:** Specifies the path to your generated DICOMweb directory.
|
||||
|
||||
:::info
|
||||
The `-p` (port) and `-o` (output directory) flags are used to configure the server.
|
||||
:::
|
||||
|
||||
## Running OHIF Viewer with Static Data
|
||||
|
||||
1. **Use the `local_static.js` Configuration:**
|
||||
|
||||
Start the OHIF Viewer in development mode using the provided `local_static.js` configuration file:
|
||||
|
||||
```bash
|
||||
yarn dev:static
|
||||
```
|
||||
|
||||
2. **Configuration Details:**
|
||||
|
||||
The `local_static.js` configuration file is pre-configured to point to:
|
||||
|
||||
```js
|
||||
qidoRoot: 'http://localhost:3001/dicomweb',
|
||||
wadoRoot: 'http://localhost:3001/dicomweb',
|
||||
```
|
||||
|
||||
This matches the default port (3001) used by the `static-wado-webserver`.
|
||||
|
||||
:::info
|
||||
If you change the port or output directory when running the `static-wado-webserver`, you **must** also update the `qidoRoot` and `wadoRoot` values in your `local_static.js` configuration file accordingly to ensure the OHIF Viewer can access the data.
|
||||
:::
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
[build.environment]
|
||||
# If 'production', `yarn install` does not install devDependencies
|
||||
NODE_ENV = "development"
|
||||
NODE_VERSION = "18.16.1"
|
||||
NODE_VERSION = "20.18.1"
|
||||
YARN_VERSION = "1.22.5"
|
||||
RUBY_VERSION = "2.6.2"
|
||||
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user