diff options
35 files changed, 561 insertions, 336 deletions
diff --git a/.github/actions/reusable-deploy/action.yml b/.github/actions/reusable-deploy/action.yml new file mode 100644 index 000000000..bc69a2e43 --- /dev/null +++ b/.github/actions/reusable-deploy/action.yml | |||
@@ -0,0 +1,46 @@ | |||
1 | name: "Reusable deploy on builds.joinpeertube.org" | ||
2 | |||
3 | description: "Reusable deploy on builds.joinpeertube.org" | ||
4 | |||
5 | inputs: | ||
6 | source: | ||
7 | required: true | ||
8 | description: "Source file/files/directory/directories to deploy" | ||
9 | destination: | ||
10 | required: true | ||
11 | description: "Destination directory on builds.joinpeertube.org" | ||
12 | knownHosts: | ||
13 | required: true | ||
14 | description: "Known hosts" | ||
15 | deployKey: | ||
16 | required: true | ||
17 | description: "Deploy key" | ||
18 | deployUser: | ||
19 | required: true | ||
20 | description: "Deploy user" | ||
21 | deployHost: | ||
22 | required: true | ||
23 | description: "Deploy host" | ||
24 | |||
25 | |||
26 | runs: | ||
27 | using: "composite" | ||
28 | |||
29 | steps: | ||
30 | - name: "Deploy" | ||
31 | shell: bash | ||
32 | run: | | ||
33 | mkdir -p ~/.ssh | ||
34 | chmod 700 ~/.ssh | ||
35 | |||
36 | echo "Adding ssh key to known hosts" | ||
37 | echo -e "${{ inputs.knownHosts }}" > ~/.ssh/known_hosts; | ||
38 | |||
39 | eval `ssh-agent -s` | ||
40 | |||
41 | echo "Adding ssh deploy key" | ||
42 | ssh-add <(echo "${{ inputs.deployKey }}"); | ||
43 | |||
44 | echo "Uploading files" | ||
45 | |||
46 | scp ${{ inputs.source }} ${{ inputs.deployUser }}@${{ inputs.deployHost }}:../../web/${{ inputs.destination }}; | ||
diff --git a/.github/actions/reusable-prepare-peertube-build/action.yml b/.github/actions/reusable-prepare-peertube-build/action.yml new file mode 100644 index 000000000..41ebf71c5 --- /dev/null +++ b/.github/actions/reusable-prepare-peertube-build/action.yml | |||
@@ -0,0 +1,31 @@ | |||
1 | name: "Reusable prepare PeerTube build" | ||
2 | |||
3 | description: "Reusable prepare PeerTube build" | ||
4 | |||
5 | inputs: | ||
6 | node-version: | ||
7 | required: true | ||
8 | description: 'NodeJS version' | ||
9 | |||
10 | runs: | ||
11 | using: "composite" | ||
12 | |||
13 | steps: | ||
14 | - name: Use Node.js | ||
15 | uses: actions/setup-node@v1 | ||
16 | with: | ||
17 | node-version: ${{ inputs.node-version }} | ||
18 | |||
19 | - name: Cache Node.js modules | ||
20 | uses: actions/cache@v2 | ||
21 | with: | ||
22 | path: | | ||
23 | **/node_modules | ||
24 | key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }} | ||
25 | restore-keys: | | ||
26 | ${{ runner.OS }}-node- | ||
27 | ${{ runner.OS }}- | ||
28 | |||
29 | - name: Install dependencies | ||
30 | shell: bash | ||
31 | run: yarn install --frozen-lockfile | ||
diff --git a/.github/actions/reusable-prepare-peertube-run/action.yml b/.github/actions/reusable-prepare-peertube-run/action.yml new file mode 100644 index 000000000..1a6cd2cfd --- /dev/null +++ b/.github/actions/reusable-prepare-peertube-run/action.yml | |||
@@ -0,0 +1,16 @@ | |||
1 | name: "Reusable prepare PeerTube run" | ||
2 | description: "Reusable prepare PeerTube run" | ||
3 | |||
4 | runs: | ||
5 | using: "composite" | ||
6 | |||
7 | steps: | ||
8 | - name: Setup system dependencies | ||
9 | shell: bash | ||
10 | run: | | ||
11 | sudo apt-get install postgresql-client-common redis-tools parallel | ||
12 | wget --quiet --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.3.1-64bit-static.tar.xz" | ||
13 | tar xf ffmpeg-release-4.3.1-64bit-static.tar.xz | ||
14 | mkdir -p $HOME/bin | ||
15 | cp ffmpeg-*/{ffmpeg,ffprobe} $HOME/bin | ||
16 | echo "$HOME/bin" >> $GITHUB_PATH | ||
diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 86f675432..7e8259d27 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml | |||
@@ -29,48 +29,15 @@ jobs: | |||
29 | env: | 29 | env: |
30 | PGUSER: peertube | 30 | PGUSER: peertube |
31 | PGHOST: localhost | 31 | PGHOST: localhost |
32 | NODE_PENDING_JOB_WAIT: 500 | ||
33 | 32 | ||
34 | steps: | 33 | steps: |
35 | - uses: actions/checkout@v2 | 34 | - uses: actions/checkout@v2 |
36 | 35 | ||
37 | - name: Use Node.js | 36 | - uses: './.github/actions/reusable-prepare-peertube-build' |
38 | uses: actions/setup-node@v1 | ||
39 | with: | 37 | with: |
40 | node-version: '12.x' | 38 | node-version: '12.x' |
41 | 39 | ||
42 | - name: Setup system dependencies | 40 | - uses: './.github/actions/reusable-prepare-peertube-run' |
43 | run: | | ||
44 | sudo apt-get install postgresql-client-common redis-tools parallel | ||
45 | wget --quiet --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.3.1-64bit-static.tar.xz" | ||
46 | tar xf ffmpeg-release-4.3.1-64bit-static.tar.xz | ||
47 | mkdir -p $HOME/bin | ||
48 | cp ffmpeg-*/{ffmpeg,ffprobe} $HOME/bin | ||
49 | echo "$HOME/bin" >> $GITHUB_PATH | ||
50 | |||
51 | - name: Cache Node.js modules | ||
52 | uses: actions/cache@v2 | ||
53 | with: | ||
54 | path: | | ||
55 | **/node_modules | ||
56 | key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }} | ||
57 | restore-keys: | | ||
58 | ${{ runner.OS }}-node- | ||
59 | ${{ runner.OS }}- | ||
60 | |||
61 | - name: Cache fixtures | ||
62 | uses: actions/cache@v2 | ||
63 | with: | ||
64 | path: | | ||
65 | fixtures | ||
66 | key: ${{ runner.OS }}-fixtures-${{ matrix.test_suite }}-${{ hashFiles('fixtures/*') }} | ||
67 | restore-keys: | | ||
68 | ${{ runner.OS }}-fixtures-${{ matrix.test_suite }}- | ||
69 | ${{ runner.OS }}-fixtures- | ||
70 | ${{ runner.OS }}- | ||
71 | |||
72 | - name: Install dependencies | ||
73 | run: yarn install --frozen-lockfile | ||
74 | 41 | ||
75 | - name: Build | 42 | - name: Build |
76 | run: | | 43 | run: | |
@@ -111,27 +78,11 @@ jobs: | |||
111 | cat benchmark.json build-time.json startup-time.json | 78 | cat benchmark.json build-time.json startup-time.json |
112 | 79 | ||
113 | - name: Upload benchmark result | 80 | - name: Upload benchmark result |
114 | env: | 81 | uses: './.github/actions/reusable-deploy' |
115 | STATS_DEPLOYEMENT_KNOWN_HOSTS: ${{ secrets.STATS_DEPLOYEMENT_KNOWN_HOSTS }} | 82 | with: |
116 | STATS_DEPLOYEMENT_KEY: ${{ secrets.STATS_DEPLOYEMENT_KEY }} | 83 | source: benchmark.json build-time.json startup-time.json |
117 | STATS_DEPLOYEMENT_USER: ${{ secrets.STATS_DEPLOYEMENT_USER }} | 84 | destination: peertube-stats |
118 | STATS_DEPLOYEMENT_HOST: ${{ secrets.STATS_DEPLOYEMENT_HOST }} | 85 | knownHosts: ${{ secrets.STATS_DEPLOYEMENT_KNOWN_HOSTS }} |
119 | run: | | 86 | deployKey: ${{ secrets.STATS_DEPLOYEMENT_KEY }} |
120 | mkdir -p ~/.ssh | 87 | deployUser: ${{ secrets.STATS_DEPLOYEMENT_USER }} |
121 | chmod 700 ~/.ssh | 88 | deployHost: ${{ secrets.STATS_DEPLOYEMENT_HOST }} |
122 | if [ ! -z ${STATS_DEPLOYEMENT_KNOWN_HOSTS+x} ]; then | ||
123 | echo "Adding ssh key to known hosts" | ||
124 | echo -e "${STATS_DEPLOYEMENT_KNOWN_HOSTS}" > ~/.ssh/known_hosts; | ||
125 | fi | ||
126 | |||
127 | eval `ssh-agent -s` | ||
128 | |||
129 | if [ ! -z ${STATS_DEPLOYEMENT_KEY+x} ]; then | ||
130 | echo "Adding ssh reployement key" | ||
131 | ssh-add <(echo "${STATS_DEPLOYEMENT_KEY}"); | ||
132 | fi | ||
133 | |||
134 | if [ ! -z ${STATS_DEPLOYEMENT_KEY+x} ]; then | ||
135 | echo "Uploading files" | ||
136 | scp benchmark.json build-time.json startup-time.json ${STATS_DEPLOYEMENT_USER}@${STATS_DEPLOYEMENT_HOST}:../../web/peertube-stats; | ||
137 | fi | ||
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..8764cdd0e --- /dev/null +++ b/.github/workflows/codeql.yml | |||
@@ -0,0 +1,68 @@ | |||
1 | # For most projects, this workflow file will not need changing; you simply need | ||
2 | # to commit it to your repository. | ||
3 | # | ||
4 | # You may wish to alter this file to override the set of languages analyzed, | ||
5 | # or to provide custom queries or build logic. | ||
6 | # | ||
7 | # ******** NOTE ******** | ||
8 | # We have attempted to detect the languages in your repository. Please check | ||
9 | # the `language` matrix defined below to confirm you have the correct set of | ||
10 | # supported CodeQL languages. | ||
11 | # | ||
12 | name: "CodeQL" | ||
13 | |||
14 | on: | ||
15 | push: | ||
16 | branches: [ develop, next ] | ||
17 | schedule: | ||
18 | - cron: '36 9 * * 5' | ||
19 | |||
20 | jobs: | ||
21 | analyze: | ||
22 | name: Analyze | ||
23 | runs-on: ubuntu-latest | ||
24 | permissions: | ||
25 | actions: read | ||
26 | contents: read | ||
27 | security-events: write | ||
28 | |||
29 | strategy: | ||
30 | fail-fast: false | ||
31 | matrix: | ||
32 | language: [ 'javascript' ] | ||
33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] | ||
34 | # Learn more about CodeQL language support at https://git.io/codeql-language-support | ||
35 | |||
36 | steps: | ||
37 | - name: Checkout repository | ||
38 | uses: actions/checkout@v2 | ||
39 | |||
40 | # Initializes the CodeQL tools for scanning. | ||
41 | - name: Initialize CodeQL | ||
42 | uses: github/codeql-action/init@v1 | ||
43 | with: | ||
44 | languages: ${{ matrix.language }} | ||
45 | config-file: ./.github/workflows/codeql/codeql-config.yml | ||
46 | # If you wish to specify custom queries, you can do so here or in a config file. | ||
47 | # By default, queries listed here will override any specified in a config file. | ||
48 | # Prefix the list here with "+" to use these queries and those in the config file. | ||
49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main | ||
50 | |||
51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). | ||
52 | # If this step fails, then you should remove it and run the build manually (see below) | ||
53 | - name: Autobuild | ||
54 | uses: github/codeql-action/autobuild@v1 | ||
55 | |||
56 | # ℹ️ Command-line programs to run using the OS shell. | ||
57 | # 📚 https://git.io/JvXDl | ||
58 | |||
59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines | ||
60 | # and modify them (or add more) to build your code if your project | ||
61 | # uses a compiled language | ||
62 | |||
63 | #- run: | | ||
64 | # make bootstrap | ||
65 | # make release | ||
66 | |||
67 | - name: Perform CodeQL Analysis | ||
68 | uses: github/codeql-action/analyze@v1 | ||
diff --git a/.github/workflows/codeql/codeql-config.yml b/.github/workflows/codeql/codeql-config.yml new file mode 100644 index 000000000..8b771ae99 --- /dev/null +++ b/.github/workflows/codeql/codeql-config.yml | |||
@@ -0,0 +1,4 @@ | |||
1 | name: "PeerTube CodeQL config" | ||
2 | |||
3 | paths-ignore: | ||
4 | - server/tests | ||
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 000000000..7afe641b3 --- /dev/null +++ b/.github/workflows/docker.yml | |||
@@ -0,0 +1,70 @@ | |||
1 | name: Docker | ||
2 | |||
3 | on: | ||
4 | push: | ||
5 | branches: | ||
6 | - 'master' | ||
7 | schedule: | ||
8 | - cron: '0 3 * * *' | ||
9 | |||
10 | jobs: | ||
11 | generate-matrix: | ||
12 | name: Generate matrix for Docker build | ||
13 | runs-on: ubuntu-latest | ||
14 | outputs: | ||
15 | matrix: ${{ steps.set-matrix.outputs.matrix }} | ||
16 | steps: | ||
17 | - name: Checkout | ||
18 | uses: actions/checkout@v2 | ||
19 | with: | ||
20 | ref: master | ||
21 | - name: Set matrix for build | ||
22 | id: set-matrix | ||
23 | run: | | ||
24 | # FIXME: https://github.com/actions/checkout/issues/290 | ||
25 | git fetch --force --tags | ||
26 | |||
27 | one="{ \"file\": \"./support/docker/production/Dockerfile.bullseye\", \"ref\": \"develop\", \"tags\": \"chocobozzz/peertube:develop-bullseye\" }" | ||
28 | two="{ \"file\": \"./support/docker/production/Dockerfile.buster\", \"ref\": \"master\", \"tags\": \"chocobozzz/peertube:production-buster,chocobozzz/peertube:$(git describe --abbrev=0)-buster\" }" | ||
29 | three="{ \"file\": \"./support/docker/production/Dockerfile.nginx\", \"ref\": \"master\", \"tags\": \"chocobozzz/peertube-webserver:latest\" }" | ||
30 | |||
31 | matrix="[$one,$two,$three]" | ||
32 | echo ::set-output name=matrix::{\"include\":$(echo $matrix)} | ||
33 | |||
34 | docker: | ||
35 | runs-on: ubuntu-latest | ||
36 | |||
37 | needs: generate-matrix | ||
38 | |||
39 | strategy: | ||
40 | matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} | ||
41 | fail-fast: false | ||
42 | |||
43 | steps: | ||
44 | - | ||
45 | name: Set up QEMU | ||
46 | uses: docker/setup-qemu-action@v1 | ||
47 | - | ||
48 | name: Set up Docker Buildx | ||
49 | uses: docker/setup-buildx-action@v1 | ||
50 | - | ||
51 | name: Login to DockerHub | ||
52 | uses: docker/login-action@v1 | ||
53 | with: | ||
54 | username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
55 | password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
56 | |||
57 | - | ||
58 | name: Checkout develop | ||
59 | uses: actions/checkout@v2 | ||
60 | with: | ||
61 | ref: ${{ matrix.ref }} | ||
62 | - | ||
63 | name: Docker build | ||
64 | uses: docker/build-push-action@v2 | ||
65 | with: | ||
66 | context: '.' | ||
67 | platforms: linux/amd64,linux/arm64 | ||
68 | push: true | ||
69 | file: ${{ matrix.file }} | ||
70 | tags: ${{ matrix.tags }} | ||
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..23898b7ef --- /dev/null +++ b/.github/workflows/nightly.yml | |||
@@ -0,0 +1,33 @@ | |||
1 | name: Nightly | ||
2 | |||
3 | on: | ||
4 | schedule: | ||
5 | - cron: '0 3 * * *' | ||
6 | |||
7 | jobs: | ||
8 | |||
9 | nightly: | ||
10 | runs-on: ubuntu-latest | ||
11 | |||
12 | steps: | ||
13 | - | ||
14 | name: Checkout develop | ||
15 | uses: actions/checkout@v2 | ||
16 | with: | ||
17 | ref: develop | ||
18 | |||
19 | - uses: './.github/actions/reusable-prepare-peertube-build' | ||
20 | with: | ||
21 | node-version: '14.x' | ||
22 | |||
23 | - name: Build | ||
24 | run: npm run nightly | ||
25 | |||
26 | - uses: './.github/actions/reusable-deploy' | ||
27 | with: | ||
28 | source: ./peertube-nightly-* | ||
29 | destination: nightly | ||
30 | knownHosts: ${{ secrets.STATS_DEPLOYEMENT_KNOWN_HOSTS }} | ||
31 | deployKey: ${{ secrets.STATS_DEPLOYEMENT_KEY }} | ||
32 | deployUser: ${{ secrets.STATS_DEPLOYEMENT_USER }} | ||
33 | deployHost: ${{ secrets.STATS_DEPLOYEMENT_HOST }} | ||
diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml index e211f6a3b..c87e6fb77 100644 --- a/.github/workflows/stats.yml +++ b/.github/workflows/stats.yml | |||
@@ -1,4 +1,4 @@ | |||
1 | name: "Stats" | 1 | name: Stats |
2 | 2 | ||
3 | on: | 3 | on: |
4 | push: | 4 | push: |
@@ -20,24 +20,10 @@ jobs: | |||
20 | steps: | 20 | steps: |
21 | - uses: actions/checkout@v2 | 21 | - uses: actions/checkout@v2 |
22 | 22 | ||
23 | - name: Use Node.js | 23 | - uses: './.github/actions/reusable-prepare-peertube-build' |
24 | uses: actions/setup-node@v1 | ||
25 | with: | 24 | with: |
26 | node-version: '14.x' | 25 | node-version: '14.x' |
27 | 26 | ||
28 | - name: Cache Node.js modules | ||
29 | uses: actions/cache@v2 | ||
30 | with: | ||
31 | path: | | ||
32 | **/node_modules | ||
33 | key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }} | ||
34 | restore-keys: | | ||
35 | ${{ runner.OS }}-node- | ||
36 | ${{ runner.OS }}- | ||
37 | |||
38 | - name: Install dependencies | ||
39 | run: yarn install --frozen-lockfile | ||
40 | |||
41 | - name: Angular bundlewatch | 27 | - name: Angular bundlewatch |
42 | uses: jackyef/bundlewatch-gh-action@master | 28 | uses: jackyef/bundlewatch-gh-action@master |
43 | with: | 29 | with: |
@@ -73,27 +59,11 @@ jobs: | |||
73 | 59 | ||
74 | - name: Upload stats | 60 | - name: Upload stats |
75 | if: github.event_name != 'pull_request' | 61 | if: github.event_name != 'pull_request' |
76 | env: | 62 | uses: './.github/actions/reusable-deploy' |
77 | STATS_DEPLOYEMENT_KNOWN_HOSTS: ${{ secrets.STATS_DEPLOYEMENT_KNOWN_HOSTS }} | 63 | with: |
78 | STATS_DEPLOYEMENT_KEY: ${{ secrets.STATS_DEPLOYEMENT_KEY }} | 64 | source: lighthouse.json client-build-stats.json scc.json |
79 | STATS_DEPLOYEMENT_USER: ${{ secrets.STATS_DEPLOYEMENT_USER }} | 65 | destination: peertube-stats |
80 | STATS_DEPLOYEMENT_HOST: ${{ secrets.STATS_DEPLOYEMENT_HOST }} | 66 | knownHosts: ${{ secrets.STATS_DEPLOYEMENT_KNOWN_HOSTS }} |
81 | run: | | 67 | deployKey: ${{ secrets.STATS_DEPLOYEMENT_KEY }} |
82 | mkdir -p ~/.ssh | 68 | deployUser: ${{ secrets.STATS_DEPLOYEMENT_USER }} |
83 | chmod 700 ~/.ssh | 69 | deployHost: ${{ secrets.STATS_DEPLOYEMENT_HOST }} |
84 | if [ ! -z ${STATS_DEPLOYEMENT_KNOWN_HOSTS+x} ]; then | ||
85 | echo "Adding ssh key to known hosts" | ||
86 | echo -e "${STATS_DEPLOYEMENT_KNOWN_HOSTS}" > ~/.ssh/known_hosts; | ||
87 | fi | ||
88 | |||
89 | eval `ssh-agent -s` | ||
90 | |||
91 | if [ ! -z ${STATS_DEPLOYEMENT_KEY+x} ]; then | ||
92 | echo "Adding ssh reployement key" | ||
93 | ssh-add <(echo "${STATS_DEPLOYEMENT_KEY}"); | ||
94 | fi | ||
95 | |||
96 | if [ ! -z ${STATS_DEPLOYEMENT_KEY+x} ]; then | ||
97 | echo "Uploading files" | ||
98 | scp lighthouse.json client-build-stats.json scc.json ${STATS_DEPLOYEMENT_USER}@${STATS_DEPLOYEMENT_HOST}:../../web/peertube-stats; | ||
99 | fi | ||
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78a9a28c0..030ec3790 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml | |||
@@ -1,4 +1,4 @@ | |||
1 | name: Test Suite | 1 | name: Test |
2 | 2 | ||
3 | on: | 3 | on: |
4 | push: | 4 | push: |
@@ -50,29 +50,11 @@ jobs: | |||
50 | steps: | 50 | steps: |
51 | - uses: actions/checkout@v2 | 51 | - uses: actions/checkout@v2 |
52 | 52 | ||
53 | - name: Use Node.js | 53 | - uses: './.github/actions/reusable-prepare-peertube-build' |
54 | uses: actions/setup-node@v1 | ||
55 | with: | 54 | with: |
56 | node-version: '12.x' | 55 | node-version: '12.x' |
57 | 56 | ||
58 | - name: Setup system dependencies | 57 | - uses: './.github/actions/reusable-prepare-peertube-run' |
59 | run: | | ||
60 | sudo apt-get install postgresql-client-common redis-tools parallel | ||
61 | wget --quiet --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.3.1-64bit-static.tar.xz" | ||
62 | tar xf ffmpeg-release-4.3.1-64bit-static.tar.xz | ||
63 | mkdir -p $HOME/bin | ||
64 | cp ffmpeg-*/{ffmpeg,ffprobe} $HOME/bin | ||
65 | echo "$HOME/bin" >> $GITHUB_PATH | ||
66 | |||
67 | - name: Cache Node.js modules | ||
68 | uses: actions/cache@v2 | ||
69 | with: | ||
70 | path: | | ||
71 | **/node_modules | ||
72 | key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }} | ||
73 | restore-keys: | | ||
74 | ${{ runner.OS }}-node- | ||
75 | ${{ runner.OS }}- | ||
76 | 58 | ||
77 | - name: Cache fixtures | 59 | - name: Cache fixtures |
78 | uses: actions/cache@v2 | 60 | uses: actions/cache@v2 |
@@ -85,9 +67,6 @@ jobs: | |||
85 | ${{ runner.OS }}-fixtures- | 67 | ${{ runner.OS }}-fixtures- |
86 | ${{ runner.OS }}- | 68 | ${{ runner.OS }}- |
87 | 69 | ||
88 | - name: Install dependencies | ||
89 | run: yarn install --frozen-lockfile | ||
90 | |||
91 | - name: Set env test variable (schedule) | 70 | - name: Set env test variable (schedule) |
92 | if: github.event_name != 'schedule' | 71 | if: github.event_name != 'schedule' |
93 | run: | | 72 | run: | |
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index ad94c8cab..000000000 --- a/.gitlab-ci.yml +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
1 | image: chocobozzz/peertube-ci:14 | ||
2 | |||
3 | stages: | ||
4 | - clients | ||
5 | - docker-nightly | ||
6 | |||
7 | cache: | ||
8 | key: yarn | ||
9 | paths: | ||
10 | - .yarn-cache | ||
11 | - cached-fixtures | ||
12 | |||
13 | # build-openapi-clients: | ||
14 | # stage: clients | ||
15 | # only: | ||
16 | # refs: | ||
17 | # - master | ||
18 | # - schedules | ||
19 | # changes: | ||
20 | # - support/doc/api/openapi.yaml | ||
21 | # script: | ||
22 | # - apt-get update -qq | ||
23 | # - apt-get -yqqq install openjdk-8-jre | ||
24 | # - yarn install --pure-lockfile | ||
25 | # - scripts/openapi-peertube-version.sh | ||
26 | # - scripts/openapi-clients.sh | ||
27 | |||
28 | build-nightly: | ||
29 | stage: docker-nightly | ||
30 | only: | ||
31 | - schedules | ||
32 | script: | ||
33 | - yarn install --pure-lockfile --cache-folder .yarn-cache | ||
34 | - npm run nightly | ||
35 | - mkdir "${HOME}/.ssh" | ||
36 | - chmod 700 "${HOME}/.ssh" | ||
37 | - if [ ! -z ${DEPLOYEMENT_KNOWN_HOSTS+x} ]; then echo -e "${DEPLOYEMENT_KNOWN_HOSTS}" > ${HOME}/.ssh/known_hosts; fi | ||
38 | - eval `ssh-agent -s` | ||
39 | - if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then ssh-add <(echo "${DEPLOYEMENT_KEY}"); fi | ||
40 | - if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then scp ./peertube-nightly-* ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:../../web/nightly; fi | ||
41 | |||
42 | .docker: &docker | ||
43 | stage: docker-nightly | ||
44 | cache: {} | ||
45 | image: | ||
46 | name: gcr.io/kaniko-project/executor:debug | ||
47 | entrypoint: [""] | ||
48 | before_script: | ||
49 | - mkdir -p /kaniko/.docker | ||
50 | - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > /kaniko/.docker/config.json | ||
51 | script: | ||
52 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $DOCKERFILE --destination $DOCKER_IMAGE_NAME | ||
53 | |||
54 | build-docker-develop: | ||
55 | <<: *docker | ||
56 | only: | ||
57 | - schedules | ||
58 | variables: | ||
59 | DOCKER_IMAGE_NAME: chocobozzz/peertube:develop-bullseye | ||
60 | DOCKERFILE: $CI_PROJECT_DIR/support/docker/production/Dockerfile.bullseye | ||
61 | |||
62 | build-docker-webserver: | ||
63 | <<: *docker | ||
64 | only: | ||
65 | - schedules | ||
66 | variables: | ||
67 | DOCKER_IMAGE_NAME: chocobozzz/peertube-webserver | ||
68 | DOCKERFILE: $CI_PROJECT_DIR/support/docker/production/Dockerfile.nginx | ||
69 | |||
70 | build-docker-tag: | ||
71 | <<: *docker | ||
72 | only: | ||
73 | - tags | ||
74 | variables: | ||
75 | DOCKER_IMAGE_NAME: chocobozzz/peertube:$CI_COMMIT_TAG-bullseye | ||
76 | DOCKERFILE: $CI_PROJECT_DIR/support/docker/production/Dockerfile.bullseye | ||
77 | |||
78 | build-docker-master: | ||
79 | <<: *docker | ||
80 | only: | ||
81 | - master | ||
82 | variables: | ||
83 | DOCKER_IMAGE_NAME: chocobozzz/peertube:production-bullseye | ||
84 | DOCKERFILE: $CI_PROJECT_DIR/support/docker/production/Dockerfile.bullseye | ||
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index 1026c4e0d..7f2a6aa77 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -116,95 +116,99 @@ | |||
116 | <my-custom-markup-container [content]="descriptionContent"></my-custom-markup-container> | 116 | <my-custom-markup-container [content]="descriptionContent"></my-custom-markup-container> |
117 | </div> | 117 | </div> |
118 | 118 | ||
119 | <div class="anchor" id="moderation"></div> | 119 | <div myPluginSelector pluginSelectorId="about-instance-moderation"> |
120 | <a | 120 | <div class="anchor" id="moderation"></div> |
121 | *ngIf="html.moderationInformation || html.codeOfConduct || html.terms" | ||
122 | class="anchor-link" | ||
123 | routerLink="/about/instance" | ||
124 | fragment="moderation" | ||
125 | #anchorLink | ||
126 | (click)="onClickCopyLink(anchorLink)"> | ||
127 | <h2 i18n class="middle-title"> | ||
128 | MODERATION | ||
129 | </h2> | ||
130 | </a> | ||
131 | |||
132 | <div class="block moderation-information" *ngIf="html.moderationInformation"> | ||
133 | <div class="anchor" id="moderation-information"></div> | ||
134 | <a | 121 | <a |
122 | *ngIf="html.moderationInformation || html.codeOfConduct || html.terms" | ||
135 | class="anchor-link" | 123 | class="anchor-link" |
136 | routerLink="/about/instance" | 124 | routerLink="/about/instance" |
137 | fragment="moderation-information" | 125 | fragment="moderation" |
138 | #anchorLink | 126 | #anchorLink |
139 | (click)="onClickCopyLink(anchorLink)"> | 127 | (click)="onClickCopyLink(anchorLink)"> |
140 | <h3 i18n class="section-title">Moderation information</h3> | 128 | <h2 i18n class="middle-title"> |
129 | MODERATION | ||
130 | </h2> | ||
141 | </a> | 131 | </a> |
142 | 132 | ||
143 | <div [innerHTML]="html.moderationInformation"></div> | 133 | <div class="block moderation-information" *ngIf="html.moderationInformation"> |
144 | </div> | 134 | <div class="anchor" id="moderation-information"></div> |
135 | <a | ||
136 | class="anchor-link" | ||
137 | routerLink="/about/instance" | ||
138 | fragment="moderation-information" | ||
139 | #anchorLink | ||
140 | (click)="onClickCopyLink(anchorLink)"> | ||
141 | <h3 i18n class="section-title">Moderation information</h3> | ||
142 | </a> | ||
145 | 143 | ||
146 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> | 144 | <div [innerHTML]="html.moderationInformation"></div> |
147 | <div class="anchor" id="code-of-conduct"></div> | 145 | </div> |
148 | <a | ||
149 | class="anchor-link" | ||
150 | routerLink="/about/instance" | ||
151 | fragment="code-of-conduct" | ||
152 | #anchorLink | ||
153 | (click)="onClickCopyLink(anchorLink)"> | ||
154 | <h3 i18n class="section-title">Code of conduct</h3> | ||
155 | </a> | ||
156 | 146 | ||
157 | <div [innerHTML]="html.codeOfConduct"></div> | 147 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> |
158 | </div> | 148 | <div class="anchor" id="code-of-conduct"></div> |
149 | <a | ||
150 | class="anchor-link" | ||
151 | routerLink="/about/instance" | ||
152 | fragment="code-of-conduct" | ||
153 | #anchorLink | ||
154 | (click)="onClickCopyLink(anchorLink)"> | ||
155 | <h3 i18n class="section-title">Code of conduct</h3> | ||
156 | </a> | ||
159 | 157 | ||
160 | <div class="block terms"> | 158 | <div [innerHTML]="html.codeOfConduct"></div> |
161 | <div class="anchor" id="terms"></div> | 159 | </div> |
162 | <a | ||
163 | class="anchor-link" | ||
164 | routerLink="/about/instance" | ||
165 | fragment="terms" | ||
166 | #anchorLink | ||
167 | (click)="onClickCopyLink(anchorLink)"> | ||
168 | <h3 i18n class="section-title">Terms</h3> | ||
169 | </a> | ||
170 | 160 | ||
171 | <div [innerHTML]="html.terms"></div> | 161 | <div class="block terms"> |
172 | </div> | 162 | <div class="anchor" id="terms"></div> |
163 | <a | ||
164 | class="anchor-link" | ||
165 | routerLink="/about/instance" | ||
166 | fragment="terms" | ||
167 | #anchorLink | ||
168 | (click)="onClickCopyLink(anchorLink)"> | ||
169 | <h3 i18n class="section-title">Terms</h3> | ||
170 | </a> | ||
173 | 171 | ||
174 | <div class="anchor" id="other-information"></div> | 172 | <div [innerHTML]="html.terms"></div> |
175 | <a | 173 | </div> |
176 | *ngIf="html.hardwareInformation" | 174 | </div> |
177 | class="anchor-link" | ||
178 | routerLink="/about/instance" | ||
179 | fragment="other-information" | ||
180 | #anchorLink | ||
181 | (click)="onClickCopyLink(anchorLink)"> | ||
182 | <h2 i18n class="middle-title"> | ||
183 | OTHER INFORMATION | ||
184 | </h2> | ||
185 | </a> | ||
186 | 175 | ||
187 | <div class="block hardware-information" *ngIf="html.hardwareInformation"> | 176 | <div myPluginSelector pluginSelectorId="about-instance-other-information"> |
188 | <div class="anchor" id="hardware-information"></div> | 177 | <div class="anchor" id="other-information"></div> |
189 | <a | 178 | <a |
179 | *ngIf="html.hardwareInformation" | ||
190 | class="anchor-link" | 180 | class="anchor-link" |
191 | routerLink="/about/instance" | 181 | routerLink="/about/instance" |
192 | fragment="hardware-information" | 182 | fragment="other-information" |
193 | #anchorLink | 183 | #anchorLink |
194 | (click)="onClickCopyLink(anchorLink)"> | 184 | (click)="onClickCopyLink(anchorLink)"> |
195 | <h3 i18n class="section-title">Hardware information</h3> | 185 | <h2 i18n class="middle-title"> |
186 | OTHER INFORMATION | ||
187 | </h2> | ||
196 | </a> | 188 | </a> |
197 | 189 | ||
198 | <div [innerHTML]="html.hardwareInformation"></div> | 190 | <div class="block hardware-information" *ngIf="html.hardwareInformation"> |
191 | <div class="anchor" id="hardware-information"></div> | ||
192 | <a | ||
193 | class="anchor-link" | ||
194 | routerLink="/about/instance" | ||
195 | fragment="hardware-information" | ||
196 | #anchorLink | ||
197 | (click)="onClickCopyLink(anchorLink)"> | ||
198 | <h3 i18n class="section-title">Hardware information</h3> | ||
199 | </a> | ||
200 | |||
201 | <div [innerHTML]="html.hardwareInformation"></div> | ||
202 | </div> | ||
199 | </div> | 203 | </div> |
200 | </div> | 204 | </div> |
201 | 205 | ||
202 | <div class="col-md-12 col-xl-6"> | 206 | <div class="col-md-12 col-xl-6" myPluginSelector pluginSelectorId="about-instance-features"> |
203 | <h2 class="sr-only" i18n>FEATURES</h2> | 207 | <h2 class="sr-only" i18n>FEATURES</h2> |
204 | <my-instance-features-table></my-instance-features-table> | 208 | <my-instance-features-table></my-instance-features-table> |
205 | </div> | 209 | </div> |
206 | 210 | ||
207 | <div class="col"> | 211 | <div class="col" myPluginSelector pluginSelectorId="about-instance-statistics"> |
208 | <div class="anchor" id="statistics"></div> | 212 | <div class="anchor" id="statistics"></div> |
209 | <a | 213 | <a |
210 | class="anchor-link" | 214 | class="anchor-link" |
diff --git a/client/src/app/+about/about.component.html b/client/src/app/+about/about.component.html index 1ab00c5df..63d429ebf 100644 --- a/client/src/app/+about/about.component.html +++ b/client/src/app/+about/about.component.html | |||
@@ -2,11 +2,11 @@ | |||
2 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }"> | 2 | <div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed }"> |
3 | 3 | ||
4 | <div class="links"> | 4 | <div class="links"> |
5 | <a i18n routerLink="instance" routerLinkActive="active" class="title-page title-page-about">Instance</a> | 5 | <a myPluginSelector pluginSelectorId="about-menu-instance" i18n routerLink="instance" routerLinkActive="active" class="title-page title-page-about">Instance</a> |
6 | 6 | ||
7 | <a i18n routerLink="peertube" routerLinkActive="active" class="title-page title-page-about">PeerTube</a> | 7 | <a myPluginSelector pluginSelectorId="about-menu-peertube" i18n routerLink="peertube" routerLinkActive="active" class="title-page title-page-about">PeerTube</a> |
8 | 8 | ||
9 | <a i18n routerLink="follows" routerLinkActive="active" class="title-page title-page-about">Network</a> | 9 | <a myPluginSelector pluginSelectorId="about-menu-network" i18n routerLink="follows" routerLinkActive="active" class="title-page title-page-about">Network</a> |
10 | </div> | 10 | </div> |
11 | </div> | 11 | </div> |
12 | 12 | ||
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html index 90eea1505..531b06dc9 100644 --- a/client/src/app/+login/login.component.html +++ b/client/src/app/+login/login.component.html | |||
@@ -48,7 +48,8 @@ | |||
48 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> | 48 | <input type="submit" class="peertube-button orange-button" i18n-value value="Login" [disabled]="!form.valid"> |
49 | 49 | ||
50 | <div class="additionnal-links"> | 50 | <div class="additionnal-links"> |
51 | <a i18n class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> | 51 | <a i18n role="button" class="forgot-password-button" (click)="openForgotPasswordModal()" i18n-title title="Click here to reset your password">I forgot my password</a> |
52 | |||
52 | <div *ngIf="signupAllowed" class="signup-link"> | 53 | <div *ngIf="signupAllowed" class="signup-link"> |
53 | <span>·</span> | 54 | <span>·</span> |
54 | <a i18n routerLink="/signup" class="create-an-account">Create an account</a> | 55 | <a i18n routerLink="/signup" class="create-an-account">Create an account</a> |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 46dd807ec..55fc27b5f 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -30,7 +30,10 @@ | |||
30 | 30 | ||
31 | <div class="dropdown-divider"></div> | 31 | <div class="dropdown-divider"></div> |
32 | 32 | ||
33 | <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()"> | 33 | <a |
34 | myPluginSelector pluginSelectorId="menu-user-dropdown-language-item" | ||
35 | ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()" | ||
36 | > | ||
34 | <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> | 37 | <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> |
35 | <span i18n>Interface:</span> | 38 | <span i18n>Interface:</span> |
36 | <span class="ml-auto text-muted">{{ currentInterfaceLanguage }}</span> | 39 | <span class="ml-auto text-muted">{{ currentInterfaceLanguage }}</span> |
diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts index 71c31696a..2aa691f05 100644 --- a/client/src/assets/player/p2p-media-loader/hls-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts | |||
@@ -146,7 +146,9 @@ class Html5Hlsjs { | |||
146 | } | 146 | } |
147 | 147 | ||
148 | duration () { | 148 | duration () { |
149 | return this._duration || this.videoElement.duration || 0 | 149 | if (!isNaN(this.videoElement.duration)) return this.videoElement.duration |
150 | |||
151 | return this._duration || 0 | ||
150 | } | 152 | } |
151 | 153 | ||
152 | seekable () { | 154 | seekable () { |
@@ -366,6 +368,7 @@ class Html5Hlsjs { | |||
366 | 368 | ||
367 | this.isLive = data.details.live | 369 | this.isLive = data.details.live |
368 | this.dvrDuration = data.details.totalduration | 370 | this.dvrDuration = data.details.totalduration |
371 | |||
369 | this._duration = this.isLive ? Infinity : data.details.totalduration | 372 | this._duration = this.isLive ? Infinity : data.details.totalduration |
370 | }) | 373 | }) |
371 | 374 | ||
diff --git a/config/test.yaml b/config/test.yaml index 2e7f982d3..461e1b4ba 100644 --- a/config/test.yaml +++ b/config/test.yaml | |||
@@ -155,6 +155,7 @@ search: | |||
155 | federation: | 155 | federation: |
156 | videos: | 156 | videos: |
157 | federate_unlisted: true | 157 | federate_unlisted: true |
158 | cleanup_remote_interactions: false | ||
158 | 159 | ||
159 | views: | 160 | views: |
160 | videos: | 161 | videos: |
diff --git a/package.json b/package.json index 6cc00d1ae..b03f5dcdb 100644 --- a/package.json +++ b/package.json | |||
@@ -90,7 +90,6 @@ | |||
90 | "cookie-parser": "^1.4.3", | 90 | "cookie-parser": "^1.4.3", |
91 | "cors": "^2.8.1", | 91 | "cors": "^2.8.1", |
92 | "create-torrent": "^5.0.0", | 92 | "create-torrent": "^5.0.0", |
93 | "decache": "^4.6.0", | ||
94 | "deep-object-diff": "^1.1.0", | 93 | "deep-object-diff": "^1.1.0", |
95 | "email-templates": "^8.0.3", | 94 | "email-templates": "^8.0.3", |
96 | "execa": "^5.1.1", | 95 | "execa": "^5.1.1", |
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index 2de7fe41f..de9e055dc 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts | |||
@@ -144,8 +144,13 @@ async function installPlugin (req: express.Request, res: express.Response) { | |||
144 | 144 | ||
145 | const fromDisk = !!body.path | 145 | const fromDisk = !!body.path |
146 | const toInstall = body.npmName || body.path | 146 | const toInstall = body.npmName || body.path |
147 | |||
148 | const pluginVersion = body.pluginVersion && body.npmName | ||
149 | ? body.pluginVersion | ||
150 | : undefined | ||
151 | |||
147 | try { | 152 | try { |
148 | const plugin = await PluginManager.Instance.install(toInstall, undefined, fromDisk) | 153 | const plugin = await PluginManager.Instance.install(toInstall, pluginVersion, fromDisk) |
149 | 154 | ||
150 | return res.json(plugin.toFormattedJSON()) | 155 | return res.json(plugin.toFormattedJSON()) |
151 | } catch (err) { | 156 | } catch (err) { |
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts index 6773b500f..c827f6bf0 100644 --- a/server/controllers/api/videos/upload.ts +++ b/server/controllers/api/videos/upload.ts | |||
@@ -8,6 +8,7 @@ import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | |||
8 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' | 8 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' |
9 | import { generateWebTorrentVideoFilename } from '@server/lib/paths' | 9 | import { generateWebTorrentVideoFilename } from '@server/lib/paths' |
10 | import { Redis } from '@server/lib/redis' | 10 | import { Redis } from '@server/lib/redis' |
11 | import { uploadx } from '@server/lib/uploadx' | ||
11 | import { | 12 | import { |
12 | addMoveToObjectStorageJob, | 13 | addMoveToObjectStorageJob, |
13 | addOptimizeOrMergeAudioJob, | 14 | addOptimizeOrMergeAudioJob, |
@@ -19,7 +20,6 @@ import { VideoPathManager } from '@server/lib/video-path-manager' | |||
19 | import { buildNextVideoState } from '@server/lib/video-state' | 20 | import { buildNextVideoState } from '@server/lib/video-state' |
20 | import { openapiOperationDoc } from '@server/middlewares/doc' | 21 | import { openapiOperationDoc } from '@server/middlewares/doc' |
21 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' | 22 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' |
22 | import { Uploadx } from '@uploadx/core' | ||
23 | import { VideoCreate, VideoState } from '../../../../shared' | 23 | import { VideoCreate, VideoState } from '../../../../shared' |
24 | import { HttpStatusCode } from '../../../../shared/models' | 24 | import { HttpStatusCode } from '../../../../shared/models' |
25 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 25 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
@@ -41,8 +41,8 @@ import { | |||
41 | authenticate, | 41 | authenticate, |
42 | videosAddLegacyValidator, | 42 | videosAddLegacyValidator, |
43 | videosAddResumableInitValidator, | 43 | videosAddResumableInitValidator, |
44 | videosResumableUploadIdValidator, | 44 | videosAddResumableValidator, |
45 | videosAddResumableValidator | 45 | videosResumableUploadIdValidator |
46 | } from '../../../middlewares' | 46 | } from '../../../middlewares' |
47 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 47 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
48 | import { VideoModel } from '../../../models/video/video' | 48 | import { VideoModel } from '../../../models/video/video' |
@@ -52,9 +52,6 @@ const lTags = loggerTagsFactory('api', 'video') | |||
52 | const auditLogger = auditLoggerFactory('videos') | 52 | const auditLogger = auditLoggerFactory('videos') |
53 | const uploadRouter = express.Router() | 53 | const uploadRouter = express.Router() |
54 | 54 | ||
55 | const uploadx = new Uploadx({ directory: getResumableUploadPath() }) | ||
56 | uploadx.getUserId = (_, res: express.Response) => res.locals.oauth?.token.user.id | ||
57 | |||
58 | const reqVideoFileAdd = createReqFiles( | 55 | const reqVideoFileAdd = createReqFiles( |
59 | [ 'videofile', 'thumbnailfile', 'previewfile' ], | 56 | [ 'videofile', 'thumbnailfile', 'previewfile' ], |
60 | Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), | 57 | Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), |
diff --git a/server/helpers/decache.ts b/server/helpers/decache.ts new file mode 100644 index 000000000..e31973b7a --- /dev/null +++ b/server/helpers/decache.ts | |||
@@ -0,0 +1,78 @@ | |||
1 | // Thanks: https://github.com/dwyl/decache | ||
2 | // We reuse this file to also uncache plugin base path | ||
3 | |||
4 | import { extname } from 'path' | ||
5 | |||
6 | function decachePlugin (pluginPath: string, libraryPath: string) { | ||
7 | const moduleName = find(libraryPath) | ||
8 | |||
9 | if (!moduleName) return | ||
10 | |||
11 | searchCache(moduleName, function (mod) { | ||
12 | delete require.cache[mod.id] | ||
13 | }) | ||
14 | |||
15 | removeCachedPath(pluginPath) | ||
16 | } | ||
17 | |||
18 | function decacheModule (name: string) { | ||
19 | const moduleName = find(name) | ||
20 | |||
21 | if (!moduleName) return | ||
22 | |||
23 | searchCache(moduleName, function (mod) { | ||
24 | delete require.cache[mod.id] | ||
25 | }) | ||
26 | |||
27 | removeCachedPath(moduleName) | ||
28 | } | ||
29 | |||
30 | // --------------------------------------------------------------------------- | ||
31 | |||
32 | export { | ||
33 | decacheModule, | ||
34 | decachePlugin | ||
35 | } | ||
36 | |||
37 | // --------------------------------------------------------------------------- | ||
38 | |||
39 | function find (moduleName: string) { | ||
40 | try { | ||
41 | return require.resolve(moduleName) | ||
42 | } catch { | ||
43 | return '' | ||
44 | } | ||
45 | } | ||
46 | |||
47 | function searchCache (moduleName: string, callback: (current: NodeModule) => void) { | ||
48 | const resolvedModule = require.resolve(moduleName) | ||
49 | let mod: NodeModule | ||
50 | const visited = {} | ||
51 | |||
52 | if (resolvedModule && ((mod = require.cache[resolvedModule]) !== undefined)) { | ||
53 | // Recursively go over the results | ||
54 | (function run (current) { | ||
55 | visited[current.id] = true | ||
56 | |||
57 | current.children.forEach(function (child) { | ||
58 | if (extname(child.filename) !== '.node' && !visited[child.id]) { | ||
59 | run(child) | ||
60 | } | ||
61 | }) | ||
62 | |||
63 | // Call the specified callback providing the | ||
64 | // found module | ||
65 | callback(current) | ||
66 | })(mod) | ||
67 | } | ||
68 | }; | ||
69 | |||
70 | function removeCachedPath (pluginPath: string) { | ||
71 | const pathCache = (module.constructor as any)._pathCache | ||
72 | |||
73 | Object.keys(pathCache).forEach(function (cacheKey) { | ||
74 | if (cacheKey.includes(pluginPath)) { | ||
75 | delete pathCache[cacheKey] | ||
76 | } | ||
77 | }) | ||
78 | } | ||
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index dadda2a77..f3a7c6b6b 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import bytes from 'bytes' | 1 | import bytes from 'bytes' |
2 | import { IConfig } from 'config' | 2 | import { IConfig } from 'config' |
3 | import decache from 'decache' | ||
4 | import { dirname, join } from 'path' | 3 | import { dirname, join } from 'path' |
4 | import { decacheModule } from '@server/helpers/decache' | ||
5 | import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' | 5 | import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' |
6 | import { BroadcastMessageLevel } from '@shared/models/server' | 6 | import { BroadcastMessageLevel } from '@shared/models/server' |
7 | import { VideosRedundancyStrategy } from '../../shared/models' | 7 | import { VideosRedundancyStrategy } from '../../shared/models' |
@@ -497,7 +497,7 @@ export function reloadConfig () { | |||
497 | delete require.cache[fileName] | 497 | delete require.cache[fileName] |
498 | } | 498 | } |
499 | 499 | ||
500 | decache('config') | 500 | decacheModule('config') |
501 | } | 501 | } |
502 | 502 | ||
503 | purge() | 503 | purge() |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index b8633e83e..c61c01d62 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -223,7 +223,7 @@ const SCHEDULER_INTERVALS_MS = { | |||
223 | REMOVE_OLD_VIEWS: 60000 * 60 * 24, // 1 day | 223 | REMOVE_OLD_VIEWS: 60000 * 60 * 24, // 1 day |
224 | REMOVE_OLD_HISTORY: 60000 * 60 * 24, // 1 day | 224 | REMOVE_OLD_HISTORY: 60000 * 60 * 24, // 1 day |
225 | UPDATE_INBOX_STATS: 1000 * 60, // 1 minute | 225 | UPDATE_INBOX_STATS: 1000 * 60, // 1 minute |
226 | REMOVE_DANGLING_RESUMABLE_UPLOADS: 60000 * 60 * 16 // 16 hours | 226 | REMOVE_DANGLING_RESUMABLE_UPLOADS: 60000 * 60 // 1 hour |
227 | } | 227 | } |
228 | 228 | ||
229 | // --------------------------------------------------------------------------- | 229 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 360b4667f..adc3d712e 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -350,10 +350,6 @@ class ClientHtml { | |||
350 | return join(__dirname, '../../../client/dist/standalone/videos/embed.html') | 350 | return join(__dirname, '../../../client/dist/standalone/videos/embed.html') |
351 | } | 351 | } |
352 | 352 | ||
353 | private static addHtmlLang (htmlStringPage: string, paramLang: string) { | ||
354 | return htmlStringPage.replace('<html>', `<html lang="${paramLang}">`) | ||
355 | } | ||
356 | |||
357 | private static addManifestContentHash (htmlStringPage: string) { | 353 | private static addManifestContentHash (htmlStringPage: string) { |
358 | return htmlStringPage.replace('[manifestContentHash]', FILES_CONTENT_HASH.MANIFEST) | 354 | return htmlStringPage.replace('[manifestContentHash]', FILES_CONTENT_HASH.MANIFEST) |
359 | } | 355 | } |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index d4d2a7edc..6c2f4764e 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import decache from 'decache' | ||
2 | import express from 'express' | 1 | import express from 'express' |
3 | import { createReadStream, createWriteStream } from 'fs' | 2 | import { createReadStream, createWriteStream } from 'fs' |
4 | import { ensureDir, outputFile, readJSON } from 'fs-extra' | 3 | import { ensureDir, outputFile, readJSON } from 'fs-extra' |
5 | import { basename, join } from 'path' | 4 | import { basename, join } from 'path' |
5 | import { decachePlugin } from '@server/helpers/decache' | ||
6 | import { MOAuthTokenUser, MUser } from '@server/types/models' | 6 | import { MOAuthTokenUser, MUser } from '@server/types/models' |
7 | import { getCompleteLocale } from '@shared/core-utils' | 7 | import { getCompleteLocale } from '@shared/core-utils' |
8 | import { ClientScript, PluginPackageJson, PluginTranslation, PluginTranslationPaths, RegisterServerHookOptions } from '@shared/models' | 8 | import { ClientScript, PluginPackageJson, PluginTranslation, PluginTranslationPaths, RegisterServerHookOptions } from '@shared/models' |
@@ -420,7 +420,7 @@ export class PluginManager implements ServerHook { | |||
420 | 420 | ||
421 | // Delete cache if needed | 421 | // Delete cache if needed |
422 | const modulePath = join(pluginPath, packageJSON.library) | 422 | const modulePath = join(pluginPath, packageJSON.library) |
423 | decache(modulePath) | 423 | decachePlugin(pluginPath, modulePath) |
424 | const library: PluginLibrary = require(modulePath) | 424 | const library: PluginLibrary = require(modulePath) |
425 | 425 | ||
426 | if (!isLibraryCodeValid(library)) { | 426 | if (!isLibraryCodeValid(library)) { |
diff --git a/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts b/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts index d6e561cad..61e93eafa 100644 --- a/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts +++ b/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { map } from 'bluebird' | 1 | |
2 | import { readdir, remove, stat } from 'fs-extra' | ||
3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
4 | import { getResumableUploadPath } from '@server/helpers/upload' | ||
5 | import { SCHEDULER_INTERVALS_MS } from '@server/initializers/constants' | 3 | import { SCHEDULER_INTERVALS_MS } from '@server/initializers/constants' |
6 | import { METAFILE_EXTNAME } from '@uploadx/core' | 4 | import { uploadx } from '../uploadx' |
7 | import { AbstractScheduler } from './abstract-scheduler' | 5 | import { AbstractScheduler } from './abstract-scheduler' |
8 | 6 | ||
9 | const lTags = loggerTagsFactory('scheduler', 'resumable-upload', 'cleaner') | 7 | const lTags = loggerTagsFactory('scheduler', 'resumable-upload', 'cleaner') |
@@ -22,36 +20,17 @@ export class RemoveDanglingResumableUploadsScheduler extends AbstractScheduler { | |||
22 | } | 20 | } |
23 | 21 | ||
24 | protected async internalExecute () { | 22 | protected async internalExecute () { |
25 | const path = getResumableUploadPath() | 23 | logger.debug('Removing dangling resumable uploads', lTags()) |
26 | const files = await readdir(path) | ||
27 | |||
28 | const metafiles = files.filter(f => f.endsWith(METAFILE_EXTNAME)) | ||
29 | 24 | ||
30 | if (metafiles.length === 0) return | 25 | const now = new Date().getTime() |
31 | |||
32 | logger.debug('Reading resumable video upload folder %s with %d files', path, metafiles.length, lTags()) | ||
33 | 26 | ||
34 | try { | 27 | try { |
35 | await map(metafiles, metafile => { | 28 | // Remove files that were not updated since the last execution |
36 | return this.deleteIfOlderThan(metafile, this.lastExecutionTimeMs) | 29 | await uploadx.storage.purge(now - this.lastExecutionTimeMs) |
37 | }, { concurrency: 5 }) | ||
38 | } catch (error) { | 30 | } catch (error) { |
39 | logger.error('Failed to handle file during resumable video upload folder cleanup', { error, ...lTags() }) | 31 | logger.error('Failed to handle file during resumable video upload folder cleanup', { error, ...lTags() }) |
40 | } finally { | 32 | } finally { |
41 | this.lastExecutionTimeMs = new Date().getTime() | 33 | this.lastExecutionTimeMs = now |
42 | } | ||
43 | } | ||
44 | |||
45 | private async deleteIfOlderThan (metafile: string, olderThan: number) { | ||
46 | const metafilePath = getResumableUploadPath(metafile) | ||
47 | const statResult = await stat(metafilePath) | ||
48 | |||
49 | // Delete uploads that started since a long time | ||
50 | if (statResult.ctimeMs < olderThan) { | ||
51 | await remove(metafilePath) | ||
52 | |||
53 | const datafile = metafilePath.replace(new RegExp(`${METAFILE_EXTNAME}$`), '') | ||
54 | await remove(datafile) | ||
55 | } | 34 | } |
56 | } | 35 | } |
57 | 36 | ||
diff --git a/server/lib/uploadx.ts b/server/lib/uploadx.ts new file mode 100644 index 000000000..11b1044db --- /dev/null +++ b/server/lib/uploadx.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import express from 'express' | ||
2 | import { getResumableUploadPath } from '@server/helpers/upload' | ||
3 | import { Uploadx } from '@uploadx/core' | ||
4 | |||
5 | const uploadx = new Uploadx({ directory: getResumableUploadPath() }) | ||
6 | uploadx.getUserId = (_, res: express.Response) => res.locals.oauth?.token.user.id | ||
7 | |||
8 | export { | ||
9 | uploadx | ||
10 | } | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 21171af23..c1e9ebefb 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -116,6 +116,9 @@ const installOrUpdatePluginValidator = [ | |||
116 | body('npmName') | 116 | body('npmName') |
117 | .optional() | 117 | .optional() |
118 | .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | 118 | .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), |
119 | body('pluginVersion') | ||
120 | .optional() | ||
121 | .custom(isPluginVersionValid).withMessage('Should have a valid plugin version'), | ||
119 | body('path') | 122 | body('path') |
120 | .optional() | 123 | .optional() |
121 | .custom(isSafePath).withMessage('Should have a valid safe path'), | 124 | .custom(isSafePath).withMessage('Should have a valid safe path'), |
@@ -129,6 +132,9 @@ const installOrUpdatePluginValidator = [ | |||
129 | if (!body.path && !body.npmName) { | 132 | if (!body.path && !body.npmName) { |
130 | return res.fail({ message: 'Should have either a npmName or a path' }) | 133 | return res.fail({ message: 'Should have either a npmName or a path' }) |
131 | } | 134 | } |
135 | if (body.pluginVersion && !body.npmName) { | ||
136 | return res.fail({ message: 'Should have a npmName when specifying a pluginVersion' }) | ||
137 | } | ||
132 | 138 | ||
133 | return next() | 139 | return next() |
134 | } | 140 | } |
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts index f2a984962..3ac440f84 100644 --- a/server/tests/cli/peertube.ts +++ b/server/tests/cli/peertube.ts | |||
@@ -207,6 +207,25 @@ describe('Test CLI wrapper', function () { | |||
207 | 207 | ||
208 | expect(res).to.not.contain('peertube-plugin-hello-world') | 208 | expect(res).to.not.contain('peertube-plugin-hello-world') |
209 | }) | 209 | }) |
210 | |||
211 | it('Should install a plugin in requested version', async function () { | ||
212 | this.timeout(60000) | ||
213 | |||
214 | await cliCommand.execWithEnv(`${cmd} plugins install --npm-name peertube-plugin-hello-world --plugin-version 0.0.17`) | ||
215 | }) | ||
216 | |||
217 | it('Should list installed plugins, in correct version', async function () { | ||
218 | const res = await cliCommand.execWithEnv(`${cmd} plugins list`) | ||
219 | |||
220 | expect(res).to.contain('peertube-plugin-hello-world') | ||
221 | expect(res).to.contain('0.0.17') | ||
222 | }) | ||
223 | |||
224 | it('Should uninstall the plugin again', async function () { | ||
225 | const res = await cliCommand.execWithEnv(`${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`) | ||
226 | |||
227 | expect(res).to.not.contain('peertube-plugin-hello-world') | ||
228 | }) | ||
210 | }) | 229 | }) |
211 | 230 | ||
212 | describe('Manage video redundancies', function () { | 231 | describe('Manage video redundancies', function () { |
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts index ae625114d..9dd3f08c9 100644 --- a/server/tools/peertube-plugins.ts +++ b/server/tools/peertube-plugins.ts | |||
@@ -31,6 +31,7 @@ program | |||
31 | .option('-p, --password <token>', 'Password') | 31 | .option('-p, --password <token>', 'Password') |
32 | .option('-P --path <path>', 'Install from a path') | 32 | .option('-P --path <path>', 'Install from a path') |
33 | .option('-n, --npm-name <npmName>', 'Install from npm') | 33 | .option('-n, --npm-name <npmName>', 'Install from npm') |
34 | .option('--plugin-version <pluginVersion>', 'Specify the plugin version to install (only available when installing from npm)') | ||
34 | .action((options, command) => installPluginCLI(command, options)) | 35 | .action((options, command) => installPluginCLI(command, options)) |
35 | 36 | ||
36 | program | 37 | program |
@@ -109,7 +110,7 @@ async function installPluginCLI (command: Command, options: OptionValues) { | |||
109 | await assignToken(server, username, password) | 110 | await assignToken(server, username, password) |
110 | 111 | ||
111 | try { | 112 | try { |
112 | await server.plugins.install({ npmName: options.npmName, path: options.path }) | 113 | await server.plugins.install({ npmName: options.npmName, path: options.path, pluginVersion: options.pluginVersion }) |
113 | } catch (err) { | 114 | } catch (err) { |
114 | console.error('Cannot install plugin.', err) | 115 | console.error('Cannot install plugin.', err) |
115 | process.exit(-1) | 116 | process.exit(-1) |
diff --git a/shared/extra-utils/server/plugins-command.ts b/shared/extra-utils/server/plugins-command.ts index b944475a2..9bf24afff 100644 --- a/shared/extra-utils/server/plugins-command.ts +++ b/shared/extra-utils/server/plugins-command.ts | |||
@@ -158,15 +158,16 @@ export class PluginsCommand extends AbstractCommand { | |||
158 | install (options: OverrideCommandOptions & { | 158 | install (options: OverrideCommandOptions & { |
159 | path?: string | 159 | path?: string |
160 | npmName?: string | 160 | npmName?: string |
161 | pluginVersion?: string | ||
161 | }) { | 162 | }) { |
162 | const { npmName, path } = options | 163 | const { npmName, path, pluginVersion } = options |
163 | const apiPath = '/api/v1/plugins/install' | 164 | const apiPath = '/api/v1/plugins/install' |
164 | 165 | ||
165 | return this.postBodyRequest({ | 166 | return this.postBodyRequest({ |
166 | ...options, | 167 | ...options, |
167 | 168 | ||
168 | path: apiPath, | 169 | path: apiPath, |
169 | fields: { npmName, path }, | 170 | fields: { npmName, path, pluginVersion }, |
170 | implicitToken: true, | 171 | implicitToken: true, |
171 | defaultExpectedStatus: HttpStatusCode.OK_200 | 172 | defaultExpectedStatus: HttpStatusCode.OK_200 |
172 | }) | 173 | }) |
diff --git a/shared/models/plugins/client/plugin-selector-id.type.ts b/shared/models/plugins/client/plugin-selector-id.type.ts index b74dffbef..8d23314b5 100644 --- a/shared/models/plugins/client/plugin-selector-id.type.ts +++ b/shared/models/plugins/client/plugin-selector-id.type.ts | |||
@@ -1 +1,10 @@ | |||
1 | export type PluginSelectorId = 'login-form' | 1 | export type PluginSelectorId = |
2 | 'login-form' | | ||
3 | 'menu-user-dropdown-language-item' | | ||
4 | 'about-instance-features' | | ||
5 | 'about-instance-statistics' | | ||
6 | 'about-instance-moderation' | | ||
7 | 'about-menu-instance' | | ||
8 | 'about-menu-peertube' | | ||
9 | 'about-menu-network' | | ||
10 | 'about-instance-other-information' | ||
diff --git a/shared/models/plugins/server/api/install-plugin.model.ts b/shared/models/plugins/server/api/install-plugin.model.ts index 5a268ebe1..a1d009a00 100644 --- a/shared/models/plugins/server/api/install-plugin.model.ts +++ b/shared/models/plugins/server/api/install-plugin.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export interface InstallOrUpdatePlugin { | 1 | export interface InstallOrUpdatePlugin { |
2 | npmName?: string | 2 | npmName?: string |
3 | pluginVersion?: string | ||
3 | path?: string | 4 | path?: string |
4 | } | 5 | } |
diff --git a/support/doc/development/ci.md b/support/doc/development/ci.md new file mode 100644 index 000000000..7d6eef197 --- /dev/null +++ b/support/doc/development/ci.md | |||
@@ -0,0 +1,40 @@ | |||
1 | # Continuous integration | ||
2 | |||
3 | PeerTube uses Github Actions as a CI platform. | ||
4 | CI tasks are described in `.github/workflows`. | ||
5 | |||
6 | ## benchmark.yml | ||
7 | |||
8 | *Scheduled* | ||
9 | |||
10 | Run various benchmarks (build, API etc) and upload results on https://builds.joinpeertube.org/peertube-stats/ to be publicly consumed. | ||
11 | |||
12 | ## codeql.yml | ||
13 | |||
14 | *Scheduled, on push on develop and on pull request* | ||
15 | |||
16 | Run CodeQL task to throw code security issues in Github. https://lgtm.com/projects/g/Chocobozzz/PeerTube can also be used. | ||
17 | |||
18 | ## docker.yml | ||
19 | |||
20 | *Scheduled and on push on master* | ||
21 | |||
22 | Build `chocobozzz/peertube-webserver:latest`, `chocobozzz/peertube:production-...`, `chocobozzz/peertube:v-...` (only latest PeerTube tag) and `chocobozzz/peertube:develop-...` Docker images. Scheduled to automatically upgrade image software (Debian security issues etc). | ||
23 | |||
24 | ## nightly.yml | ||
25 | |||
26 | *Scheduled* | ||
27 | |||
28 | Build PeerTube nightly build (`develop` branch) and upload the release on https://builds.joinpeertube.org/nightly. | ||
29 | |||
30 | ## stats.yml | ||
31 | |||
32 | *On push on develop* | ||
33 | |||
34 | Create various PeerTube stats (line of codes, build size, lighthouse report) and upload results on https://builds.joinpeertube.org/peertube-stats/ to be publicly consumed. | ||
35 | |||
36 | ## test.yml | ||
37 | |||
38 | *Scheduled, on push and pull request* | ||
39 | |||
40 | Run PeerTube lint and tests. | ||
@@ -2613,11 +2613,6 @@ call-me-maybe@^1.0.1: | |||
2613 | resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" | 2613 | resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" |
2614 | integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= | 2614 | integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= |
2615 | 2615 | ||
2616 | callsite@^1.0.0: | ||
2617 | version "1.0.0" | ||
2618 | resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" | ||
2619 | integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= | ||
2620 | |||
2621 | callsites@^3.0.0: | 2616 | callsites@^3.0.0: |
2622 | version "3.1.0" | 2617 | version "3.1.0" |
2623 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" | 2618 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" |
@@ -3229,13 +3224,6 @@ debuglog@^1.0.0: | |||
3229 | resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" | 3224 | resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" |
3230 | integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= | 3225 | integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= |
3231 | 3226 | ||
3232 | decache@^4.6.0: | ||
3233 | version "4.6.0" | ||
3234 | resolved "https://registry.yarnpkg.com/decache/-/decache-4.6.0.tgz#87026bc6e696759e82d57a3841c4e251a30356e8" | ||
3235 | integrity sha512-PppOuLiz+DFeaUvFXEYZjLxAkKiMYH/do/b/MxpDe/8AgKBi5GhZxridoVIbBq72GDbL36e4p0Ce2jTGUwwU+w== | ||
3236 | dependencies: | ||
3237 | callsite "^1.0.0" | ||
3238 | |||
3239 | decamelize@^1.2.0: | 3227 | decamelize@^1.2.0: |
3240 | version "1.2.0" | 3228 | version "1.2.0" |
3241 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" | 3229 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" |