Migrate server to ESM

Sorry for the very big commit that may lead to git log issues and merge
conflicts, but it's a major step forward:

 * Server can be faster at startup because imports() are async and we can
   easily lazy import big modules
 * Angular doesn't seem to support ES import (with .js extension), so we
   had to correctly organize peertube into a monorepo:
    * Use yarn workspace feature
    * Use typescript reference projects for dependencies
    * Shared projects have been moved into "packages", each one is now a
      node module (with a dedicated package.json/tsconfig.json)
    * server/tools have been moved into apps/ and is now a dedicated app
      bundled and published on NPM so users don't have to build peertube
      cli tools manually
    * server/tests have been moved into packages/ so we don't compile
      them every time we want to run the server
 * Use isolatedModule option:
   * Had to move from const enum to const
     (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums)
   * Had to explictely specify "type" imports when used in decorators
 * Prefer tsx (that uses esbuild under the hood) instead of ts-node to
   load typescript files (tests with mocha or scripts):
     * To reduce test complexity as esbuild doesn't support decorator
       metadata, we only test server files that do not import server
       models
     * We still build tests files into js files for a faster CI
 * Remove unmaintained peertube CLI import script
 * Removed some barrels to speed up execution (less imports)
This commit is contained in:
Chocobozzz 2023-07-31 14:34:36 +02:00
parent 04d1da5621
commit 3a4992633e
No known key found for this signature in database
GPG key ID: 583A612D890159BE
2196 changed files with 12690 additions and 11574 deletions

View file

@ -1,5 +1,6 @@
{ {
"extends": "standard-with-typescript", "extends": "standard-with-typescript",
"root": true,
"rules": { "rules": {
"eol-last": [ "eol-last": [
"error", "error",
@ -126,18 +127,20 @@
] ]
}, },
"ignorePatterns": [ "ignorePatterns": [
"node_modules/", "node_modules",
"server/tests/fixtures" "packages/tests/fixtures",
"apps/**/dist",
"packages/**/dist",
"server/dist",
"packages/types-generator/tests",
"*.js",
"/client",
"/dist"
], ],
"parserOptions": { "parserOptions": {
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
"project": [ "project": [
"./tsconfig.json", "./tsconfig.eslint.json"
"./shared/tsconfig.json", ],
"./scripts/tsconfig.json", "EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true
"./server/tsconfig.json",
"./server/tools/tsconfig.json",
"./packages/peertube-runner/tsconfig.json"
]
} }
} }

View file

@ -53,13 +53,25 @@ interested in, user interface, design, decentralized architecture...
You can help to write the documentation of the REST API, code, architecture, You can help to write the documentation of the REST API, code, architecture,
demonstrations. demonstrations.
For the REST API you can see the documentation in [/support/doc/api](https://github.com/Chocobozzz/PeerTube/tree/develop/support/doc/api) directory. ### User documentation
Then, you can just open the `openapi.yaml` file in a special editor like [http://editor.swagger.io/](http://editor.swagger.io/) to easily see and edit the documentation. You can also use [redoc-cli](https://github.com/Redocly/redoc/blob/master/cli/README.md) and run `redoc-cli serve --watch support/doc/api/openapi.yaml` to see the final result.
The official user documentation is available on https://docs.joinpeertube.org/
You can update it by writing markdown files in the following repository: https://framagit.org/framasoft/peertube/documentation/
### REST API documentation
The [REST API documentation](https://docs.joinpeertube.org/api-rest-reference.html) is generated from `support/doc/api/openapi.yaml` file.
To quickly get a preview of your changes, you can generate the documentation *on the fly* using the following command:
```
npx @redocly/cli preview-docs ./support/doc/api/openapi.yaml
```
Some hints: Some hints:
* Routes are defined in [/server/controllers/](https://github.com/Chocobozzz/PeerTube/tree/develop/server/controllers) directory * Routes are defined in [/server/server/controllers/](https://github.com/Chocobozzz/PeerTube/tree/develop/server/server/controllers) directory
* Parameters validators are defined in [/server/middlewares/validators](https://github.com/Chocobozzz/PeerTube/tree/develop/server/middlewares/validators) directory * Parameters validators are defined in [/server/server/middlewares/validators](https://github.com/Chocobozzz/PeerTube/tree/develop/server/server/middlewares/validators) directory
* Models sent/received by the controllers are defined in [/shared/models](https://github.com/Chocobozzz/PeerTube/tree/develop/shared/models) directory * Models sent/received by the controllers are defined in [/packages/models](https://github.com/Chocobozzz/PeerTube/tree/develop/packages/models) directory
## Improve the website ## Improve the website
@ -242,15 +254,6 @@ To test emails with PeerTube:
* Run [mailslurper](http://mailslurper.com/) * Run [mailslurper](http://mailslurper.com/)
* Run PeerTube using mailslurper SMTP port: `NODE_CONFIG='{ "smtp": { "hostname": "localhost", "port": 2500, "tls": false } }' NODE_ENV=dev node dist/server` * Run PeerTube using mailslurper SMTP port: `NODE_CONFIG='{ "smtp": { "hostname": "localhost", "port": 2500, "tls": false } }' NODE_ENV=dev node dist/server`
### OpenAPI documentation
The [REST API documentation](https://docs.joinpeertube.org/api-rest-reference.html) is generated from `support/doc/api/openapi.yaml` file.
To quickly get a preview of your changes, you can generate the documentation *on the fly* using the following command:
```
npx @redocly/cli preview-docs ./support/doc/api/openapi.yaml
```
### Environment variables ### Environment variables
PeerTube can be configured using environment variables. PeerTube can be configured using environment variables.

View file

@ -32,4 +32,12 @@ runs:
- name: Install peertube runner dependencies - name: Install peertube runner dependencies
shell: bash shell: bash
run: cd packages/peertube-runner && yarn install --frozen-lockfile run: cd apps/peertube-runner && yarn install --frozen-lockfile
- name: Install peertube CLI dependencies
shell: bash
run: cd apps/peertube-cli && yarn install --frozen-lockfile
- name: Display PeerTube dependencies
shell: bash
run: ls -l node_modules/@peertube

View file

@ -71,7 +71,7 @@ jobs:
- name: Run benchmark - name: Run benchmark
run: | run: |
node dist/scripts/benchmark.js -o benchmark.json npm run benchmark-server -- -o benchmark.json
- name: Display result - name: Display result
run: | run: |

View file

@ -1,4 +1,4 @@
name: "PeerTube CodeQL config" name: "PeerTube CodeQL config"
paths-ignore: paths-ignore:
- server/tests - packages/tests

View file

@ -36,12 +36,12 @@ jobs:
run: | run: |
wget "https://github.com/boyter/scc/releases/download/v3.0.0/scc-3.0.0-x86_64-unknown-linux.zip" wget "https://github.com/boyter/scc/releases/download/v3.0.0/scc-3.0.0-x86_64-unknown-linux.zip"
unzip "scc-3.0.0-x86_64-unknown-linux.zip" unzip "scc-3.0.0-x86_64-unknown-linux.zip"
./scc --format=json --exclude-dir .git,node_modules,client/node_modules,client/dist,dist,yarn.lock,client/yarn.lock,client/src/locale,test1,test2,test3,client/src/assets/images,config,storage,server/tests/fixtures,support/openapi,.idea,.vscode,docker-volume,ffmpeg-3,ffmpeg-4 > ./scc.json ./scc --format=json --exclude-dir .git,node_modules,client/node_modules,client/dist,dist,yarn.lock,client/yarn.lock,client/src/locale,test1,test2,test3,client/src/assets/images,config,storage,packages/tests/fixtures,support/openapi,.idea,.vscode,docker-volume,ffmpeg-3,ffmpeg-4 > ./scc.json
- name: PeerTube client stats - name: PeerTube client stats
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
run: | run: |
node dist/scripts/client-build-stats.js > client-build-stats.json npm run client:build-stats > client-build-stats.json
- name: PeerTube client lighthouse report - name: PeerTube client lighthouse report
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'

16
.gitignore vendored
View file

@ -1,8 +1,8 @@
# NPM instalation # NPM instalation
/node_modules/ node_modules
/server/tools/node_modules
*npm-debug.log *npm-debug.log
yarn-error.log yarn-error.log
.yarn
# Testing # Testing
/test1/ /test1/
@ -11,8 +11,8 @@ yarn-error.log
/test4/ /test4/
/test5/ /test5/
/test6/ /test6/
/server/tests/fixtures/video_high_bitrate_1080p.mp4 /packages/tests/fixtures/video_high_bitrate_1080p.mp4
/server/tests/fixtures/video_59fps.mp4 /packages/tests/fixtures/video_59fps.mp4
# Production # Production
/storage /storage
@ -49,12 +49,14 @@ yarn-error.log
/*.tar.xz /*.tar.xz
/*.asc /*.asc
*.DS_Store *.DS_Store
/server/tools/import-mediacore.ts
/docker-volume/ /docker-volume/
/init.mp4 /init.mp4
# TypeScript # TypeScript
*.tsbuildinfo *.tsbuildinfo
# Packages # EsLint
/packages/types/dist/ .eslintcache
# Compiled output
dist

10
.mocharc.cjs Normal file
View file

@ -0,0 +1,10 @@
process.env.ESBK_TSCONFIG_PATH = './packages/tests/tsconfig.json'
module.exports = {
"node-option": [
"loader=tsx",
"no-warnings",
"conditions=peertube:tsx"
],
"timeout": 30000
}

View file

@ -0,0 +1,4 @@
src
meta.json
tsconfig.json
scripts

View file

@ -0,0 +1,43 @@
# PeerTube CLI
## Usage
See https://docs.joinpeertube.org/maintain/tools#remote-tools
## Dev
## Install dependencies
```bash
cd peertube-root
yarn install --pure-lockfile
cd apps/peertube-cli && yarn install --pure-lockfile
```
## Develop
```bash
cd peertube-root
npm run dev:peertube-cli
```
## Build
```bash
cd peertube-root
npm run build:peertube-cli
```
## Run
```bash
cd peertube-root
node apps/peertube-cli/dist/peertube-cli.js --help
```
## Publish on NPM
```bash
cd peertube-root
(cd apps/peertube-cli && npm version patch) && npm run build:peertube-cli && (cd apps/peertube-cli && npm publish --access=public)
```

View file

@ -0,0 +1,19 @@
{
"name": "@peertube/peertube-cli",
"version": "1.0.1",
"type": "module",
"main": "dist/peertube.js",
"bin": "dist/peertube.js",
"engines": {
"node": ">=16.x"
},
"scripts": {},
"license": "AGPL-3.0",
"private": false,
"devDependencies": {
"application-config": "^2.0.0",
"cli-table3": "^0.6.0",
"netrc-parser": "^3.1.6"
},
"dependencies": {}
}

View file

@ -0,0 +1,27 @@
import * as esbuild from 'esbuild'
import { readFileSync } from 'fs'
const packageJSON = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)))
export const esbuildOptions = {
entryPoints: [ './src/peertube.ts' ],
bundle: true,
platform: 'node',
format: 'esm',
target: 'node16',
external: [
'./lib-cov/fluent-ffmpeg',
'pg-hstore'
],
outfile: './dist/peertube.js',
banner: {
js: `const require = (await import("node:module")).createRequire(import.meta.url);` +
`const __filename = (await import("node:url")).fileURLToPath(import.meta.url);` +
`const __dirname = (await import("node:path")).dirname(__filename);`
},
define: {
'process.env.PACKAGE_VERSION': `'${packageJSON.version}'`
}
}
await esbuild.build(esbuildOptions)

View file

@ -0,0 +1,7 @@
import * as esbuild from 'esbuild'
import { esbuildOptions } from './build.js'
const context = await esbuild.context(esbuildOptions)
// Enable watch mode
await context.watch()

View file

@ -0,0 +1,171 @@
import CliTable3 from 'cli-table3'
import prompt from 'prompt'
import { Command } from '@commander-js/extra-typings'
import { assignToken, buildServer, getNetrc, getSettings, writeSettings } from './shared/index.js'
export function defineAuthProgram () {
const program = new Command()
.name('auth')
.description('Register your accounts on remote instances to use them with other commands')
program
.command('add')
.description('remember your accounts on remote instances for easier use')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('--default', 'add the entry as the new default')
.action(options => {
/* eslint-disable no-import-assign */
prompt.override = options
prompt.start()
prompt.get({
properties: {
url: {
description: 'instance url',
conform: value => isURLaPeerTubeInstance(value),
message: 'It should be an URL (https://peertube.example.com)',
required: true
},
username: {
conform: value => typeof value === 'string' && value.length !== 0,
message: 'Name must be only letters, spaces, or dashes',
required: true
},
password: {
hidden: true,
replace: '*',
required: true
}
}
}, async (_, result) => {
// Check credentials
try {
// Strip out everything after the domain:port.
// See https://github.com/Chocobozzz/PeerTube/issues/3520
result.url = stripExtraneousFromPeerTubeUrl(result.url)
const server = buildServer(result.url)
await assignToken(server, result.username, result.password)
} catch (err) {
console.error(err.message)
process.exit(-1)
}
await setInstance(result.url, result.username, result.password, options.default)
process.exit(0)
})
})
program
.command('del <url>')
.description('Unregisters a remote instance')
.action(async url => {
await delInstance(url)
process.exit(0)
})
program
.command('list')
.description('List registered remote instances')
.action(async () => {
const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
const table = new CliTable3({
head: [ 'instance', 'login' ],
colWidths: [ 30, 30 ]
}) as any
settings.remotes.forEach(element => {
if (!netrc.machines[element]) return
table.push([
element,
netrc.machines[element].login
])
})
console.log(table.toString())
process.exit(0)
})
program
.command('set-default <url>')
.description('Set an existing entry as default')
.action(async url => {
const settings = await getSettings()
const instanceExists = settings.remotes.includes(url)
if (instanceExists) {
settings.default = settings.remotes.indexOf(url)
await writeSettings(settings)
process.exit(0)
} else {
console.log('<url> is not a registered instance.')
process.exit(-1)
}
})
program.addHelpText('after', '\n\n Examples:\n\n' +
' $ peertube auth add -u https://peertube.cpy.re -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"\n' +
' $ peertube auth add -u https://peertube.cpy.re -U root\n' +
' $ peertube auth list\n' +
' $ peertube auth del https://peertube.cpy.re\n'
)
return program
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
async function delInstance (url: string) {
const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
const index = settings.remotes.indexOf(url)
settings.remotes.splice(index)
if (settings.default === index) settings.default = -1
await writeSettings(settings)
delete netrc.machines[url]
await netrc.save()
}
async function setInstance (url: string, username: string, password: string, isDefault: boolean) {
const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
if (settings.remotes.includes(url) === false) {
settings.remotes.push(url)
}
if (isDefault || settings.remotes.length === 1) {
settings.default = settings.remotes.length - 1
}
await writeSettings(settings)
netrc.machines[url] = { login: username, password }
await netrc.save()
}
function isURLaPeerTubeInstance (url: string) {
return url.startsWith('http://') || url.startsWith('https://')
}
function stripExtraneousFromPeerTubeUrl (url: string) {
// Get everything before the 3rd /.
const urlLength = url.includes('/', 8)
? url.indexOf('/', 8)
: url.length
return url.substring(0, urlLength)
}

View file

@ -0,0 +1,39 @@
import { Command } from '@commander-js/extra-typings'
import { assignToken, buildServer } from './shared/index.js'
export function defineGetAccessProgram () {
const program = new Command()
.name('get-access-token')
.description('Get a peertube access token')
.alias('token')
program
.option('-u, --url <url>', 'Server url')
.option('-n, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.action(async options => {
try {
if (
!options.url ||
!options.username ||
!options.password
) {
if (!options.url) console.error('--url field is required.')
if (!options.username) console.error('--username field is required.')
if (!options.password) console.error('--password field is required.')
process.exit(-1)
}
const server = buildServer(options.url)
await assignToken(server, options.username, options.password)
console.log(server.accessToken)
} catch (err) {
console.error('Cannot get access token: ' + err.message)
process.exit(-1)
}
})
return program
}

View file

@ -0,0 +1,167 @@
import CliTable3 from 'cli-table3'
import { isAbsolute } from 'path'
import { Command } from '@commander-js/extra-typings'
import { PluginType, PluginType_Type } from '@peertube/peertube-models'
import { assignToken, buildServer, CommonProgramOptions, getServerCredentials } from './shared/index.js'
export function definePluginsProgram () {
const program = new Command()
program
.name('plugins')
.description('Manage instance plugins/themes')
.alias('p')
program
.command('list')
.description('List installed plugins')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-t, --only-themes', 'List themes only')
.option('-P, --only-plugins', 'List plugins only')
.action(async options => {
try {
await pluginsListCLI(options)
} catch (err) {
console.error('Cannot list plugins: ' + err.message)
process.exit(-1)
}
})
program
.command('install')
.description('Install a plugin or a theme')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-P --path <path>', 'Install from a path')
.option('-n, --npm-name <npmName>', 'Install from npm')
.option('--plugin-version <pluginVersion>', 'Specify the plugin version to install (only available when installing from npm)')
.action(async options => {
try {
await installPluginCLI(options)
} catch (err) {
console.error('Cannot install plugin: ' + err.message)
process.exit(-1)
}
})
program
.command('update')
.description('Update a plugin or a theme')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-P --path <path>', 'Update from a path')
.option('-n, --npm-name <npmName>', 'Update from npm')
.action(async options => {
try {
await updatePluginCLI(options)
} catch (err) {
console.error('Cannot update plugin: ' + err.message)
process.exit(-1)
}
})
program
.command('uninstall')
.description('Uninstall a plugin or a theme')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-n, --npm-name <npmName>', 'NPM plugin/theme name')
.action(async options => {
try {
await uninstallPluginCLI(options)
} catch (err) {
console.error('Cannot uninstall plugin: ' + err.message)
process.exit(-1)
}
})
return program
}
// ----------------------------------------------------------------------------
async function pluginsListCLI (options: CommonProgramOptions & { onlyThemes?: true, onlyPlugins?: true }) {
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
let pluginType: PluginType_Type
if (options.onlyThemes) pluginType = PluginType.THEME
if (options.onlyPlugins) pluginType = PluginType.PLUGIN
const { data } = await server.plugins.list({ start: 0, count: 100, sort: 'name', pluginType })
const table = new CliTable3({
head: [ 'name', 'version', 'homepage' ],
colWidths: [ 50, 20, 50 ]
}) as any
for (const plugin of data) {
const npmName = plugin.type === PluginType.PLUGIN
? 'peertube-plugin-' + plugin.name
: 'peertube-theme-' + plugin.name
table.push([
npmName,
plugin.version,
plugin.homepage
])
}
console.log(table.toString())
}
async function installPluginCLI (options: CommonProgramOptions & { path?: string, npmName?: string, pluginVersion?: string }) {
if (!options.path && !options.npmName) {
throw new Error('You need to specify the npm name or the path of the plugin you want to install.')
}
if (options.path && !isAbsolute(options.path)) {
throw new Error('Path should be absolute.')
}
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
await server.plugins.install({ npmName: options.npmName, path: options.path, pluginVersion: options.pluginVersion })
console.log('Plugin installed.')
}
async function updatePluginCLI (options: CommonProgramOptions & { path?: string, npmName?: string }) {
if (!options.path && !options.npmName) {
throw new Error('You need to specify the npm name or the path of the plugin you want to update.')
}
if (options.path && !isAbsolute(options.path)) {
throw new Error('Path should be absolute.')
}
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
await server.plugins.update({ npmName: options.npmName, path: options.path })
console.log('Plugin updated.')
}
async function uninstallPluginCLI (options: CommonProgramOptions & { npmName?: string }) {
if (!options.npmName) {
throw new Error('You need to specify the npm name of the plugin/theme you want to uninstall.')
}
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
await server.plugins.uninstall({ npmName: options.npmName })
console.log('Plugin uninstalled.')
}

View file

@ -0,0 +1,186 @@
import bytes from 'bytes'
import CliTable3 from 'cli-table3'
import { URL } from 'url'
import { Command } from '@commander-js/extra-typings'
import { forceNumber, uniqify } from '@peertube/peertube-core-utils'
import { HttpStatusCode, VideoRedundanciesTarget } from '@peertube/peertube-models'
import { assignToken, buildServer, CommonProgramOptions, getServerCredentials } from './shared/index.js'
export function defineRedundancyProgram () {
const program = new Command()
.name('redundancy')
.description('Manage instance redundancies')
.alias('r')
program
.command('list-remote-redundancies')
.description('List remote redundancies on your videos')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.action(async options => {
try {
await listRedundanciesCLI({ target: 'my-videos', ...options })
} catch (err) {
console.error('Cannot list remote redundancies: ' + err.message)
process.exit(-1)
}
})
program
.command('list-my-redundancies')
.description('List your redundancies of remote videos')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.action(async options => {
try {
await listRedundanciesCLI({ target: 'remote-videos', ...options })
} catch (err) {
console.error('Cannot list redundancies: ' + err.message)
process.exit(-1)
}
})
program
.command('add')
.description('Duplicate a video in your redundancy system')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.requiredOption('-v, --video <videoId>', 'Video id to duplicate', parseInt)
.action(async options => {
try {
await addRedundancyCLI(options)
} catch (err) {
console.error('Cannot duplicate video: ' + err.message)
process.exit(-1)
}
})
program
.command('remove')
.description('Remove a video from your redundancies')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.requiredOption('-v, --video <videoId>', 'Video id to remove from redundancies', parseInt)
.action(async options => {
try {
await removeRedundancyCLI(options)
} catch (err) {
console.error('Cannot remove redundancy: ' + err)
process.exit(-1)
}
})
return program
}
// ----------------------------------------------------------------------------
async function listRedundanciesCLI (options: CommonProgramOptions & { target: VideoRedundanciesTarget }) {
const { target } = options
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
const { data } = await server.redundancy.listVideos({ start: 0, count: 100, sort: 'name', target })
const table = new CliTable3({
head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ]
}) as any
for (const redundancy of data) {
const webVideoFiles = redundancy.redundancies.files
const streamingPlaylists = redundancy.redundancies.streamingPlaylists
let totalSize = ''
if (target === 'remote-videos') {
const tmp = webVideoFiles.concat(streamingPlaylists)
.reduce((a, b) => a + b.size, 0)
// FIXME: don't use external dependency to stringify bytes: we already have the functions in the client
totalSize = bytes(tmp)
}
const instances = uniqify(
webVideoFiles.concat(streamingPlaylists)
.map(r => r.fileUrl)
.map(u => new URL(u).host)
)
table.push([
redundancy.id.toString(),
redundancy.name,
redundancy.url,
webVideoFiles.length,
streamingPlaylists.length,
instances.join('\n'),
totalSize
])
}
console.log(table.toString())
}
async function addRedundancyCLI (options: { video: number } & CommonProgramOptions) {
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
if (!options.video || isNaN(options.video)) {
throw new Error('You need to specify the video id to duplicate and it should be a number.')
}
try {
await server.redundancy.addVideo({ videoId: options.video })
console.log('Video will be duplicated by your instance!')
} catch (err) {
if (err.message.includes(HttpStatusCode.CONFLICT_409)) {
throw new Error('This video is already duplicated by your instance.')
}
if (err.message.includes(HttpStatusCode.NOT_FOUND_404)) {
throw new Error('This video id does not exist.')
}
throw err
}
}
async function removeRedundancyCLI (options: CommonProgramOptions & { video: number }) {
const { url, username, password } = await getServerCredentials(options)
const server = buildServer(url)
await assignToken(server, username, password)
if (!options.video || isNaN(options.video)) {
throw new Error('You need to specify the video id to remove from your redundancies')
}
const videoId = forceNumber(options.video)
const myVideoRedundancies = await server.redundancy.listVideos({ target: 'my-videos' })
let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id)
if (!videoRedundancy) {
const remoteVideoRedundancies = await server.redundancy.listVideos({ target: 'remote-videos' })
videoRedundancy = remoteVideoRedundancies.data.find(r => videoId === r.id)
}
if (!videoRedundancy) {
throw new Error('Video redundancy not found.')
}
const ids = videoRedundancy.redundancies.files
.concat(videoRedundancy.redundancies.streamingPlaylists)
.map(r => r.id)
for (const id of ids) {
await server.redundancy.removeVideo({ redundancyId: id })
}
console.log('Video redundancy removed!')
}

View file

@ -0,0 +1,167 @@
import { access, constants } from 'fs/promises'
import { isAbsolute } from 'path'
import { inspect } from 'util'
import { Command } from '@commander-js/extra-typings'
import { VideoPrivacy } from '@peertube/peertube-models'
import { PeerTubeServer } from '@peertube/peertube-server-commands'
import { assignToken, buildServer, getServerCredentials, listOptions } from './shared/index.js'
type UploadOptions = {
url?: string
username?: string
password?: string
thumbnail?: string
preview?: string
file?: string
videoName?: string
category?: string
licence?: string
language?: string
tags?: string
nsfw?: true
videoDescription?: string
privacy?: number
channelName?: string
noCommentsEnabled?: true
support?: string
noWaitTranscoding?: true
noDownloadEnabled?: true
}
export function defineUploadProgram () {
const program = new Command('upload')
.description('Upload a video on a PeerTube instance')
.alias('up')
program
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
.option('-v, --preview <previewPath>', 'Preview path')
.option('-f, --file <file>', 'Video absolute file path')
.option('-n, --video-name <name>', 'Video name')
.option('-c, --category <category_number>', 'Category number')
.option('-l, --licence <licence_number>', 'Licence number')
.option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
.option('-t, --tags <tags>', 'Video tags', listOptions)
.option('-N, --nsfw', 'Video is Not Safe For Work')
.option('-d, --video-description <description>', 'Video description')
.option('-P, --privacy <privacy_number>', 'Privacy', parseInt)
.option('-C, --channel-name <channel_name>', 'Channel name')
.option('--no-comments-enabled', 'Disable video comments')
.option('-s, --support <support>', 'Video support text')
.option('--no-wait-transcoding', 'Do not wait transcoding before publishing the video')
.option('--no-download-enabled', 'Disable video download')
.option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
.action(async options => {
try {
const { url, username, password } = await getServerCredentials(options)
if (!options.videoName || !options.file) {
if (!options.videoName) console.error('--video-name is required.')
if (!options.file) console.error('--file is required.')
process.exit(-1)
}
if (isAbsolute(options.file) === false) {
console.error('File path should be absolute.')
process.exit(-1)
}
await run({ ...options, url, username, password })
} catch (err) {
console.error('Cannot upload video: ' + err.message)
process.exit(-1)
}
})
return program
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
async function run (options: UploadOptions) {
const { url, username, password } = options
const server = buildServer(url)
await assignToken(server, username, password)
await access(options.file, constants.F_OK)
console.log('Uploading %s video...', options.videoName)
const baseAttributes = await buildVideoAttributesFromCommander(server, options)
const attributes = {
...baseAttributes,
fixture: options.file,
thumbnailfile: options.thumbnail,
previewfile: options.preview
}
try {
await server.videos.upload({ attributes })
console.log(`Video ${options.videoName} uploaded.`)
process.exit(0)
} catch (err) {
const message = err.message || ''
if (message.includes('413')) {
console.error('Aborted: user quota is exceeded or video file is too big for this PeerTube instance.')
} else {
console.error(inspect(err))
}
process.exit(-1)
}
}
async function buildVideoAttributesFromCommander (server: PeerTubeServer, options: UploadOptions, defaultAttributes: any = {}) {
const defaultBooleanAttributes = {
nsfw: false,
commentsEnabled: true,
downloadEnabled: true,
waitTranscoding: true
}
const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
for (const key of Object.keys(defaultBooleanAttributes)) {
if (options[key] !== undefined) {
booleanAttributes[key] = options[key]
} else if (defaultAttributes[key] !== undefined) {
booleanAttributes[key] = defaultAttributes[key]
} else {
booleanAttributes[key] = defaultBooleanAttributes[key]
}
}
const videoAttributes = {
name: options.videoName || defaultAttributes.name,
category: options.category || defaultAttributes.category || undefined,
licence: options.licence || defaultAttributes.licence || undefined,
language: options.language || defaultAttributes.language || undefined,
privacy: options.privacy || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
support: options.support || defaultAttributes.support || undefined,
description: options.videoDescription || defaultAttributes.description || undefined,
tags: options.tags || defaultAttributes.tags || undefined
}
Object.assign(videoAttributes, booleanAttributes)
if (options.channelName) {
const videoChannel = await server.channels.get({ channelName: options.channelName })
Object.assign(videoAttributes, { channelId: videoChannel.id })
if (!videoAttributes.support && videoChannel.support) {
Object.assign(videoAttributes, { support: videoChannel.support })
}
}
return videoAttributes
}

View file

@ -1,32 +1,24 @@
#!/usr/bin/env node #!/usr/bin/env node
import { CommandOptions, program } from 'commander' import { Command } from '@commander-js/extra-typings'
import { getSettings, version } from './shared' import { defineAuthProgram } from './peertube-auth.js'
import { defineGetAccessProgram } from './peertube-get-access-token.js'
import { definePluginsProgram } from './peertube-plugins.js'
import { defineRedundancyProgram } from './peertube-redundancy.js'
import { defineUploadProgram } from './peertube-upload.js'
import { getSettings, version } from './shared/index.js'
const program = new Command()
program program
.version(version, '-v, --version') .version(version, '-v, --version')
.usage('[command] [options]') .usage('[command] [options]')
/* Subcommands automatically loaded in the directory and beginning by peertube-* */ program.addCommand(defineAuthProgram())
program program.addCommand(defineUploadProgram())
.command('auth [action]', 'register your accounts on remote instances to use them with other commands') program.addCommand(defineRedundancyProgram())
.command('upload', 'upload a video').alias('up') program.addCommand(definePluginsProgram())
.command('import-videos', 'import a video from a streaming platform').alias('import') program.addCommand(defineGetAccessProgram())
.command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
.command('plugins [action]', 'manage instance plugins/themes').alias('p')
.command('redundancy [action]', 'manage instance redundancies').alias('r')
/* Not Yet Implemented */
program
.command(
'diagnostic [action]',
'like couple therapy, but for your instance',
{ noHelp: true } as CommandOptions
).alias('d')
.command('admin',
'manage an instance where you have elevated rights',
{ noHelp: true } as CommandOptions
).alias('a')
// help on no command // help on no command
if (!process.argv.slice(2).length) { if (!process.argv.slice(2).length) {

View file

@ -1,19 +1,23 @@
import { Command } from 'commander' import applicationConfig from 'application-config'
import { Netrc } from 'netrc-parser' import { Netrc } from 'netrc-parser'
import { join } from 'path' import { join } from 'path'
import { createLogger, format, transports } from 'winston' import { createLogger, format, transports } from 'winston'
import { getAppNumber, isTestInstance } from '@server/helpers/core-utils' import { UserRole } from '@peertube/peertube-models'
import { loadLanguages } from '@server/initializers/constants' import { getAppNumber, isTestInstance, root } from '@peertube/peertube-node-utils'
import { root } from '@shared/core-utils' import { PeerTubeServer } from '@peertube/peertube-server-commands'
import { UserRole, VideoPrivacy } from '@shared/models'
import { PeerTubeServer } from '@shared/server-commands' export type CommonProgramOptions = {
url?: string
username?: string
password?: string
}
let configName = 'PeerTube/CLI' let configName = 'PeerTube/CLI'
if (isTestInstance()) configName += `-${getAppNumber()}` if (isTestInstance()) configName += `-${getAppNumber()}`
const config = require('application-config')(configName) const config = applicationConfig(configName)
const version = require(join(root(), 'package.json')).version const version: string = process.env.PACKAGE_VERSION
async function getAdminTokenOrDie (server: PeerTubeServer, username: string, password: string) { async function getAdminTokenOrDie (server: PeerTubeServer, username: string, password: string) {
const token = await server.login.getAccessToken(username, password) const token = await server.login.getAccessToken(username, password)
@ -32,13 +36,13 @@ interface Settings {
default: number default: number
} }
async function getSettings (): Promise<Settings> { async function getSettings () {
const defaultSettings = { const defaultSettings: Settings = {
remotes: [], remotes: [],
default: -1 default: -1
} }
const data = await config.read() const data = await config.read() as Promise<Settings>
return Object.keys(data).length === 0 return Object.keys(data).length === 0
? defaultSettings ? defaultSettings
@ -46,10 +50,8 @@ async function getSettings (): Promise<Settings> {
} }
async function getNetrc () { async function getNetrc () {
const Netrc = require('netrc-parser').Netrc
const netrc = isTestInstance() const netrc = isTestInstance()
? new Netrc(join(root(), 'test' + getAppNumber(), 'netrc')) ? new Netrc(join(root(import.meta.url), 'test' + getAppNumber(), 'netrc'))
: new Netrc() : new Netrc()
await netrc.load() await netrc.load()
@ -66,11 +68,10 @@ function deleteSettings () {
} }
function getRemoteObjectOrDie ( function getRemoteObjectOrDie (
program: Command, options: CommonProgramOptions,
settings: Settings, settings: Settings,
netrc: Netrc netrc: Netrc
): { url: string, username: string, password: string } { ): { url: string, username: string, password: string } {
const options = program.opts()
function exitIfNoOptions (optionNames: string[], errorPrefix: string = '') { function exitIfNoOptions (optionNames: string[], errorPrefix: string = '') {
let exit = false let exit = false
@ -119,85 +120,18 @@ function getRemoteObjectOrDie (
return { url, username, password } return { url, username, password }
} }
function buildCommonVideoOptions (command: Command) { function listOptions (val: any) {
function list (val) { return val.split(',')
return val.split(',')
}
return command
.option('-n, --video-name <name>', 'Video name')
.option('-c, --category <category_number>', 'Category number')
.option('-l, --licence <licence_number>', 'Licence number')
.option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
.option('-t, --tags <tags>', 'Video tags', list)
.option('-N, --nsfw', 'Video is Not Safe For Work')
.option('-d, --video-description <description>', 'Video description')
.option('-P, --privacy <privacy_number>', 'Privacy')
.option('-C, --channel-name <channel_name>', 'Channel name')
.option('--no-comments-enabled', 'Disable video comments')
.option('-s, --support <support>', 'Video support text')
.option('--no-wait-transcoding', 'Do not wait transcoding before publishing the video')
.option('--no-download-enabled', 'Disable video download')
.option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
} }
async function buildVideoAttributesFromCommander (server: PeerTubeServer, command: Command, defaultAttributes: any = {}) { function getServerCredentials (options: CommonProgramOptions) {
const options = command.opts()
const defaultBooleanAttributes = {
nsfw: false,
commentsEnabled: true,
downloadEnabled: true,
waitTranscoding: true
}
const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
for (const key of Object.keys(defaultBooleanAttributes)) {
if (options[key] !== undefined) {
booleanAttributes[key] = options[key]
} else if (defaultAttributes[key] !== undefined) {
booleanAttributes[key] = defaultAttributes[key]
} else {
booleanAttributes[key] = defaultBooleanAttributes[key]
}
}
const videoAttributes = {
name: options.videoName || defaultAttributes.name,
category: options.category || defaultAttributes.category || undefined,
licence: options.licence || defaultAttributes.licence || undefined,
language: options.language || defaultAttributes.language || undefined,
privacy: options.privacy || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
support: options.support || defaultAttributes.support || undefined,
description: options.videoDescription || defaultAttributes.description || undefined,
tags: options.tags || defaultAttributes.tags || undefined
}
Object.assign(videoAttributes, booleanAttributes)
if (options.channelName) {
const videoChannel = await server.channels.get({ channelName: options.channelName })
Object.assign(videoAttributes, { channelId: videoChannel.id })
if (!videoAttributes.support && videoChannel.support) {
Object.assign(videoAttributes, { support: videoChannel.support })
}
}
return videoAttributes
}
function getServerCredentials (program: Command) {
return Promise.all([ getSettings(), getNetrc() ]) return Promise.all([ getSettings(), getNetrc() ])
.then(([ settings, netrc ]) => { .then(([ settings, netrc ]) => {
return getRemoteObjectOrDie(program, settings, netrc) return getRemoteObjectOrDie(options, settings, netrc)
}) })
} }
function buildServer (url: string) { function buildServer (url: string) {
loadLanguages()
return new PeerTubeServer({ url }) return new PeerTubeServer({ url })
} }
@ -253,8 +187,7 @@ export {
getServerCredentials, getServerCredentials,
buildCommonVideoOptions, listOptions,
buildVideoAttributesFromCommander,
getAdminTokenOrDie, getAdminTokenOrDie,
buildServer, buildServer,

View file

@ -0,0 +1 @@
export * from './cli.js'

View file

@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist",
"rootDir": "src",
"tsBuildInfoFile": "./dist/.tsbuildinfo"
},
"references": [
{ "path": "../../packages/core-utils" },
{ "path": "../../packages/models" },
{ "path": "../../packages/node-utils" },
{ "path": "../../packages/server-commands" }
]
}

View file

@ -3,26 +3,32 @@
"@babel/code-frame@^7.0.0": "@babel/code-frame@^7.0.0":
version "7.16.7" version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3"
integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==
dependencies: dependencies:
"@babel/highlight" "^7.16.7" "@babel/highlight" "^7.22.10"
chalk "^2.4.2"
"@babel/helper-validator-identifier@^7.16.7": "@babel/helper-validator-identifier@^7.22.5":
version "7.16.7" version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
"@babel/highlight@^7.16.7": "@babel/highlight@^7.22.10":
version "7.16.10" version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7"
integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==
dependencies: dependencies:
"@babel/helper-validator-identifier" "^7.16.7" "@babel/helper-validator-identifier" "^7.22.5"
chalk "^2.0.0" chalk "^2.4.2"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
ansi-regex@^5.0.1: ansi-regex@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@ -36,9 +42,9 @@ ansi-styles@^3.2.1:
color-convert "^1.9.0" color-convert "^1.9.0"
application-config-path@^0.1.0: application-config-path@^0.1.0:
version "0.1.0" version "0.1.1"
resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f" resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.1.tgz#8b5ac64ff6afdd9bd70ce69f6f64b6998f5f756e"
integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8= integrity sha512-zy9cHePtMP0YhwG+CfHm0bgwdnga2X3gZexpdCwEj//dpb+TKajtiC8REEUJUSq6Ab4f9cgNy2l8ObXzCXFkEw==
application-config@^2.0.0: application-config@^2.0.0:
version "2.0.0" version "2.0.0"
@ -49,7 +55,7 @@ application-config@^2.0.0:
load-json-file "^6.2.0" load-json-file "^6.2.0"
write-json-file "^4.2.0" write-json-file "^4.2.0"
chalk@^2.0.0: chalk@^2.4.2:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -59,13 +65,13 @@ chalk@^2.0.0:
supports-color "^5.3.0" supports-color "^5.3.0"
cli-table3@^0.6.0: cli-table3@^0.6.0:
version "0.6.1" version "0.6.3"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2"
integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==
dependencies: dependencies:
string-width "^4.2.0" string-width "^4.2.0"
optionalDependencies: optionalDependencies:
colors "1.4.0" "@colors/colors" "1.5.0"
color-convert@^1.9.0: color-convert@^1.9.0:
version "1.9.3" version "1.9.3"
@ -77,12 +83,7 @@ color-convert@^1.9.0:
color-name@1.1.3: color-name@1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
colors@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
cross-spawn@^6.0.0: cross-spawn@^6.0.0:
version "6.0.5" version "6.0.5"
@ -122,7 +123,7 @@ error-ex@^1.3.1:
escape-string-regexp@^1.0.5: escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
execa@^0.10.0: execa@^0.10.0:
version "0.10.0" version "0.10.0"
@ -140,27 +141,27 @@ execa@^0.10.0:
get-stream@^3.0.0: get-stream@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==
graceful-fs@^4.1.15: graceful-fs@^4.1.15:
version "4.2.9" version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-flag@^3.0.0: has-flag@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
imurmurhash@^0.1.4: imurmurhash@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
is-arrayish@^0.2.1: is-arrayish@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-fullwidth-code-point@^3.0.0: is-fullwidth-code-point@^3.0.0:
version "3.0.0" version "3.0.0"
@ -175,17 +176,17 @@ is-plain-obj@^2.0.0:
is-stream@^1.1.0: is-stream@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
is-typedarray@^1.0.0: is-typedarray@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
isexe@^2.0.0: isexe@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
js-tokens@^4.0.0: js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
@ -240,14 +241,14 @@ nice-try@^1.0.4:
npm-run-path@^2.0.0: npm-run-path@^2.0.0:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==
dependencies: dependencies:
path-key "^2.0.0" path-key "^2.0.0"
p-finally@^1.0.0: p-finally@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
parse-json@^5.0.0: parse-json@^5.0.0:
version "5.2.0" version "5.2.0"
@ -262,29 +263,29 @@ parse-json@^5.0.0:
path-key@^2.0.0, path-key@^2.0.1: path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
semver@^5.5.0: semver@^5.5.0:
version "5.7.1" version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@^6.0.0: semver@^6.0.0:
version "6.3.0" version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
shebang-command@^1.2.0: shebang-command@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
dependencies: dependencies:
shebang-regex "^1.0.0" shebang-regex "^1.0.0"
shebang-regex@^1.0.0: shebang-regex@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
signal-exit@^3.0.0, signal-exit@^3.0.2: signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.7" version "3.0.7"
@ -322,7 +323,7 @@ strip-bom@^4.0.0:
strip-eof@^1.0.0: strip-eof@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
supports-color@^5.3.0: supports-color@^5.3.0:
version "5.5.0" version "5.5.0"

View file

@ -0,0 +1,4 @@
src
meta.json
tsconfig.json
scripts

View file

@ -0,0 +1,43 @@
# PeerTube runner
Runner program to execute jobs (transcoding...) of remote PeerTube instances.
Commands below has to be run at the root of PeerTube git repository.
## Dev
### Install dependencies
```bash
cd peertube-root
yarn install --pure-lockfile
cd apps/peertube-runner && yarn install --pure-lockfile
```
### Develop
```bash
cd peertube-root
npm run dev:peertube-runner
```
### Build
```bash
cd peertube-root
npm run build:peertube-runner
```
### Run
```bash
cd peertube-root
node apps/peertube-runner/dist/peertube-runner.js --help
```
### Publish on NPM
```bash
cd peertube-root
(cd apps/peertube-runner && npm version patch) && npm run build:peertube-runner && (cd apps/peertube-runner && npm publish --access=public)
```

View file

@ -1,15 +1,18 @@
{ {
"name": "@peertube/peertube-runner", "name": "@peertube/peertube-runner",
"version": "0.0.5", "version": "0.0.5",
"type": "module",
"main": "dist/peertube-runner.js", "main": "dist/peertube-runner.js",
"bin": "dist/peertube-runner.js", "bin": "dist/peertube-runner.js",
"engines": {
"node": ">=16.x"
},
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@commander-js/extra-typings": "^10.0.3", "@commander-js/extra-typings": "^10.0.3",
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"env-paths": "^3.0.0", "env-paths": "^3.0.0",
"esbuild": "^0.17.15",
"net-ipc": "^2.0.1", "net-ipc": "^2.0.1",
"pino": "^8.11.0", "pino": "^8.11.0",
"pino-pretty": "^10.0.0" "pino-pretty": "^10.0.0"

View file

@ -0,0 +1,26 @@
import * as esbuild from 'esbuild'
const packageJSON = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)))
export const esbuildOptions = {
entryPoints: [ './src/peertube-runner.ts' ],
bundle: true,
platform: 'node',
format: 'esm',
target: 'node16',
external: [
'./lib-cov/fluent-ffmpeg',
'pg-hstore'
],
outfile: './dist/peertube-runner.js',
banner: {
js: `const require = (await import("node:module")).createRequire(import.meta.url);` +
`const __filename = (await import("node:url")).fileURLToPath(import.meta.url);` +
`const __dirname = (await import("node:path")).dirname(__filename);`
},
define: {
'process.env.PACKAGE_VERSION': `'${packageJSON.version}'`
}
}
await esbuild.build(esbuildOptions)

View file

@ -1,14 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Command, InvalidArgumentError } from '@commander-js/extra-typings' import { Command, InvalidArgumentError } from '@commander-js/extra-typings'
import { listRegistered, registerRunner, unregisterRunner } from './register' import { listRegistered, registerRunner, unregisterRunner } from './register/index.js'
import { RunnerServer } from './server' import { RunnerServer } from './server/index.js'
import { ConfigManager, logger } from './shared' import { ConfigManager, logger } from './shared/index.js'
const packageJSON = require('./package.json')
const program = new Command() const program = new Command()
.version(packageJSON.version) .version(process.env.PACKAGE_VERSION)
.option( .option(
'--id <id>', '--id <id>',
'Runner server id, so you can run multiple PeerTube server runners with different configurations on the same machine', 'Runner server id, so you can run multiple PeerTube server runners with different configurations on the same machine',

View file

@ -0,0 +1 @@
export * from './register.js'

View file

@ -1,4 +1,4 @@
import { IPCClient } from '../shared/ipc' import { IPCClient } from '../shared/ipc/index.js'
export async function registerRunner (options: { export async function registerRunner (options: {
url: string url: string

View file

@ -0,0 +1 @@
export * from './server.js'

View file

@ -0,0 +1,2 @@
export * from './shared/index.js'
export * from './process.js'

View file

@ -1,14 +1,14 @@
import { logger } from 'packages/peertube-runner/shared/logger'
import { import {
RunnerJobLiveRTMPHLSTranscodingPayload, RunnerJobLiveRTMPHLSTranscodingPayload,
RunnerJobStudioTranscodingPayload, RunnerJobStudioTranscodingPayload,
RunnerJobVODAudioMergeTranscodingPayload, RunnerJobVODAudioMergeTranscodingPayload,
RunnerJobVODHLSTranscodingPayload, RunnerJobVODHLSTranscodingPayload,
RunnerJobVODWebVideoTranscodingPayload RunnerJobVODWebVideoTranscodingPayload
} from '@shared/models' } from '@peertube/peertube-models'
import { processAudioMergeTranscoding, processHLSTranscoding, ProcessOptions, processWebVideoTranscoding } from './shared' import { logger } from '../../shared/index.js'
import { ProcessLiveRTMPHLSTranscoding } from './shared/process-live' import { processAudioMergeTranscoding, processHLSTranscoding, ProcessOptions, processWebVideoTranscoding } from './shared/index.js'
import { processStudioTranscoding } from './shared/process-studio' import { ProcessLiveRTMPHLSTranscoding } from './shared/process-live.js'
import { processStudioTranscoding } from './shared/process-studio.js'
export async function processJob (options: ProcessOptions) { export async function processJob (options: ProcessOptions) {
const { server, job } = options const { server, job } = options

View file

@ -1,11 +1,11 @@
import { remove } from 'fs-extra' import { remove } from 'fs-extra/esm'
import { ConfigManager, downloadFile, logger } from 'packages/peertube-runner/shared'
import { join } from 'path' import { join } from 'path'
import { buildUUID } from '@shared/extra-utils' import { FFmpegEdition, FFmpegLive, FFmpegVOD, getDefaultAvailableEncoders, getDefaultEncodersToTry } from '@peertube/peertube-ffmpeg'
import { FFmpegEdition, FFmpegLive, FFmpegVOD, getDefaultAvailableEncoders, getDefaultEncodersToTry } from '@shared/ffmpeg' import { RunnerJob, RunnerJobPayload } from '@peertube/peertube-models'
import { RunnerJob, RunnerJobPayload } from '@shared/models' import { buildUUID } from '@peertube/peertube-node-utils'
import { PeerTubeServer } from '@shared/server-commands' import { PeerTubeServer } from '@peertube/peertube-server-commands'
import { getTranscodingLogger } from './transcoding-logger' import { ConfigManager, downloadFile, logger } from '../../../shared/index.js'
import { getTranscodingLogger } from './transcoding-logger.js'
export type JobWithToken <T extends RunnerJobPayload = RunnerJobPayload> = RunnerJob<T> & { jobToken: string } export type JobWithToken <T extends RunnerJobPayload = RunnerJobPayload> = RunnerJob<T> & { jobToken: string }

View file

@ -0,0 +1,3 @@
export * from './common.js'
export * from './process-vod.js'
export * from './transcoding-logger.js'

View file

@ -1,20 +1,20 @@
import { FSWatcher, watch } from 'chokidar' import { FSWatcher, watch } from 'chokidar'
import { FfmpegCommand } from 'fluent-ffmpeg' import { FfmpegCommand } from 'fluent-ffmpeg'
import { ensureDir, remove } from 'fs-extra' import { ensureDir, remove } from 'fs-extra/esm'
import { logger } from 'packages/peertube-runner/shared'
import { basename, join } from 'path' import { basename, join } from 'path'
import { wait } from '@shared/core-utils' import { wait } from '@peertube/peertube-core-utils'
import { buildUUID } from '@shared/extra-utils' import { ffprobePromise, getVideoStreamBitrate, getVideoStreamDimensionsInfo, hasAudioStream } from '@peertube/peertube-ffmpeg'
import { ffprobePromise, getVideoStreamBitrate, getVideoStreamDimensionsInfo, hasAudioStream } from '@shared/ffmpeg'
import { import {
LiveRTMPHLSTranscodingSuccess, LiveRTMPHLSTranscodingSuccess,
LiveRTMPHLSTranscodingUpdatePayload, LiveRTMPHLSTranscodingUpdatePayload,
PeerTubeProblemDocument, PeerTubeProblemDocument,
RunnerJobLiveRTMPHLSTranscodingPayload, RunnerJobLiveRTMPHLSTranscodingPayload,
ServerErrorCode ServerErrorCode
} from '@shared/models' } from '@peertube/peertube-models'
import { ConfigManager } from '../../../shared/config-manager' import { buildUUID } from '@peertube/peertube-node-utils'
import { buildFFmpegLive, ProcessOptions } from './common' import { ConfigManager } from '../../../shared/config-manager.js'
import { logger } from '../../../shared/index.js'
import { buildFFmpegLive, ProcessOptions } from './common.js'
export class ProcessLiveRTMPHLSTranscoding { export class ProcessLiveRTMPHLSTranscoding {

View file

@ -1,8 +1,6 @@
import { remove } from 'fs-extra' import { remove } from 'fs-extra/esm'
import { logger } from 'packages/peertube-runner/shared'
import { join } from 'path' import { join } from 'path'
import { pick } from '@shared/core-utils' import { pick } from '@peertube/peertube-core-utils'
import { buildUUID } from '@shared/extra-utils'
import { import {
RunnerJobStudioTranscodingPayload, RunnerJobStudioTranscodingPayload,
VideoStudioTask, VideoStudioTask,
@ -12,9 +10,11 @@ import {
VideoStudioTaskPayload, VideoStudioTaskPayload,
VideoStudioTaskWatermarkPayload, VideoStudioTaskWatermarkPayload,
VideoStudioTranscodingSuccess VideoStudioTranscodingSuccess
} from '@shared/models' } from '@peertube/peertube-models'
import { ConfigManager } from '../../../shared/config-manager' import { buildUUID } from '@peertube/peertube-node-utils'
import { buildFFmpegEdition, downloadInputFile, JobWithToken, ProcessOptions, scheduleTranscodingProgress } from './common' import { ConfigManager } from '../../../shared/config-manager.js'
import { logger } from '../../../shared/index.js'
import { buildFFmpegEdition, downloadInputFile, JobWithToken, ProcessOptions, scheduleTranscodingProgress } from './common.js'
export async function processStudioTranscoding (options: ProcessOptions<RunnerJobStudioTranscodingPayload>) { export async function processStudioTranscoding (options: ProcessOptions<RunnerJobStudioTranscodingPayload>) {
const { server, job, runnerToken } = options const { server, job, runnerToken } = options

View file

@ -1,7 +1,5 @@
import { remove } from 'fs-extra' import { remove } from 'fs-extra/esm'
import { logger } from 'packages/peertube-runner/shared'
import { join } from 'path' import { join } from 'path'
import { buildUUID } from '@shared/extra-utils'
import { import {
RunnerJobVODAudioMergeTranscodingPayload, RunnerJobVODAudioMergeTranscodingPayload,
RunnerJobVODHLSTranscodingPayload, RunnerJobVODHLSTranscodingPayload,
@ -9,9 +7,11 @@ import {
VODAudioMergeTranscodingSuccess, VODAudioMergeTranscodingSuccess,
VODHLSTranscodingSuccess, VODHLSTranscodingSuccess,
VODWebVideoTranscodingSuccess VODWebVideoTranscodingSuccess
} from '@shared/models' } from '@peertube/peertube-models'
import { ConfigManager } from '../../../shared/config-manager' import { buildUUID } from '@peertube/peertube-node-utils'
import { buildFFmpegVOD, downloadInputFile, ProcessOptions, scheduleTranscodingProgress } from './common' import { ConfigManager } from '../../../shared/config-manager.js'
import { logger } from '../../../shared/index.js'
import { buildFFmpegVOD, downloadInputFile, ProcessOptions, scheduleTranscodingProgress } from './common.js'
export async function processWebVideoTranscoding (options: ProcessOptions<RunnerJobVODWebVideoTranscodingPayload>) { export async function processWebVideoTranscoding (options: ProcessOptions<RunnerJobVODWebVideoTranscodingPayload>) {
const { server, job, runnerToken } = options const { server, job, runnerToken } = options

View file

@ -1,4 +1,4 @@
import { logger } from 'packages/peertube-runner/shared/logger' import { logger } from '../../../shared/index.js'
export function getTranscodingLogger () { export function getTranscodingLogger () {
return { return {

View file

@ -1,14 +1,15 @@
import { ensureDir, readdir, remove } from 'fs-extra' import { ensureDir, remove } from 'fs-extra/esm'
import { readdir } from 'fs/promises'
import { join } from 'path' import { join } from 'path'
import { io, Socket } from 'socket.io-client' import { io, Socket } from 'socket.io-client'
import { pick, shuffle, wait } from '@shared/core-utils' import { pick, shuffle, wait } from '@peertube/peertube-core-utils'
import { PeerTubeProblemDocument, ServerErrorCode } from '@shared/models' import { PeerTubeProblemDocument, ServerErrorCode } from '@peertube/peertube-models'
import { PeerTubeServer as PeerTubeServerCommand } from '@shared/server-commands' import { PeerTubeServer as PeerTubeServerCommand } from '@peertube/peertube-server-commands'
import { ConfigManager } from '../shared' import { ConfigManager } from '../shared/index.js'
import { IPCServer } from '../shared/ipc' import { IPCServer } from '../shared/ipc/index.js'
import { logger } from '../shared/logger' import { logger } from '../shared/logger.js'
import { JobWithToken, processJob } from './process' import { JobWithToken, processJob } from './process/index.js'
import { isJobSupported } from './shared' import { isJobSupported } from './shared/index.js'
type PeerTubeServer = PeerTubeServerCommand & { type PeerTubeServer = PeerTubeServerCommand & {
runnerToken: string runnerToken: string

View file

@ -0,0 +1 @@
export * from './supported-job.js'

View file

@ -7,7 +7,7 @@ import {
RunnerJobVODHLSTranscodingPayload, RunnerJobVODHLSTranscodingPayload,
RunnerJobVODWebVideoTranscodingPayload, RunnerJobVODWebVideoTranscodingPayload,
VideoStudioTaskPayload VideoStudioTaskPayload
} from '@shared/models' } from '@peertube/peertube-models'
const supportedMatrix = { const supportedMatrix = {
'vod-web-video-transcoding': (_payload: RunnerJobVODWebVideoTranscodingPayload) => { 'vod-web-video-transcoding': (_payload: RunnerJobVODWebVideoTranscodingPayload) => {

View file

@ -1,9 +1,10 @@
import envPaths from 'env-paths'
import { ensureDir, pathExists, readFile, remove, writeFile } from 'fs-extra'
import { merge } from 'lodash'
import { logger } from 'packages/peertube-runner/shared/logger'
import { dirname, join } from 'path'
import { parse, stringify } from '@iarna/toml' import { parse, stringify } from '@iarna/toml'
import envPaths from 'env-paths'
import { ensureDir, pathExists, remove } from 'fs-extra/esm'
import { readFile, writeFile } from 'fs/promises'
import merge from 'lodash-es/merge.js'
import { dirname, join } from 'path'
import { logger } from '../shared/index.js'
const paths = envPaths('peertube-runner') const paths = envPaths('peertube-runner')

View file

@ -1,7 +1,8 @@
import { createWriteStream, remove } from 'fs-extra' import { createWriteStream } from 'fs'
import { remove } from 'fs-extra/esm'
import { request as requestHTTP } from 'http' import { request as requestHTTP } from 'http'
import { request as requestHTTPS, RequestOptions } from 'https' import { request as requestHTTPS, RequestOptions } from 'https'
import { logger } from './logger' import { logger } from './logger.js'
export function downloadFile (options: { export function downloadFile (options: {
url: string url: string

View file

@ -0,0 +1,3 @@
export * from './config-manager.js'
export * from './http.js'
export * from './logger.js'

View file

@ -0,0 +1,2 @@
export * from './ipc-client.js'
export * from './ipc-server.js'

View file

@ -1,8 +1,8 @@
import CliTable3 from 'cli-table3' import CliTable3 from 'cli-table3'
import { ensureDir } from 'fs-extra' import { ensureDir } from 'fs-extra/esm'
import { Client as NetIPC } from 'net-ipc' import { Client as NetIPC } from 'net-ipc'
import { ConfigManager } from '../config-manager' import { ConfigManager } from '../config-manager.js'
import { IPCReponse, IPCReponseData, IPCRequest } from './shared' import { IPCReponse, IPCReponseData, IPCRequest } from './shared/index.js'
export class IPCClient { export class IPCClient {
private netIPC: NetIPC private netIPC: NetIPC

View file

@ -1,10 +1,10 @@
import { ensureDir } from 'fs-extra' import { ensureDir } from 'fs-extra/esm'
import { Server as NetIPC } from 'net-ipc' import { Server as NetIPC } from 'net-ipc'
import { pick } from '@shared/core-utils' import { pick } from '@peertube/peertube-core-utils'
import { RunnerServer } from '../../server' import { RunnerServer } from '../../server/index.js'
import { ConfigManager } from '../config-manager' import { ConfigManager } from '../config-manager.js'
import { logger } from '../logger' import { logger } from '../logger.js'
import { IPCReponse, IPCReponseData, IPCRequest } from './shared' import { IPCReponse, IPCReponseData, IPCRequest } from './shared/index.js'
export class IPCServer { export class IPCServer {
private netIPC: NetIPC private netIPC: NetIPC

View file

@ -0,0 +1,2 @@
export * from './ipc-request.model.js'
export * from './ipc-response.model.js'

View file

@ -1,7 +1,7 @@
import { pino } from 'pino' import { pino } from 'pino'
import pretty from 'pino-pretty' import pretty from 'pino-pretty'
const logger = pino(pretty({ const logger = pino(pretty.default({
colorize: true colorize: true
})) }))

View file

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist",
"rootDir": "src",
"tsBuildInfoFile": "./dist/.tsbuildinfo"
},
"references": [
{ "path": "../../packages/core-utils" },
{ "path": "../../packages/ffmpeg" },
{ "path": "../../packages/models" },
{ "path": "../../packages/node-utils" },
{ "path": "../../packages/server-commands" }
]
}

View file

@ -14,6 +14,7 @@
"project": [ "project": [
"tsconfig.eslint.json" "tsconfig.eslint.json"
], ],
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
"createDefaultProgram": false "createDefaultProgram": false
}, },
"extends": [ "extends": [

View file

@ -107,14 +107,6 @@ export const config = {
tsNodeOpts: { tsNodeOpts: {
project: require('path').join(__dirname, './tsconfig.json') project: require('path').join(__dirname, './tsconfig.json')
},
tsConfigPathsOpts: {
baseUrl: './',
paths: {
'@server/*': [ '../../server/*' ],
'@shared/*': [ '../../shared/*' ]
}
} }
}, },

View file

@ -14,7 +14,7 @@
}, },
"scripts": { "scripts": {
"lint": "npm run lint-ts && npm run lint-scss", "lint": "npm run lint-ts && npm run lint-scss",
"lint-ts": "eslint --ext .ts src/standalone/**/*.ts && npm run ng lint", "lint-ts": "eslint --cache --ext .ts src/standalone/**/*.ts && npm run ng lint",
"lint-scss": "stylelint 'src/**/*.scss'", "lint-scss": "stylelint 'src/**/*.scss'",
"webpack": "webpack", "webpack": "webpack",
"eslint": "eslint", "eslint": "eslint",
@ -24,6 +24,9 @@
"ngx-extractor": "ngx-extractor", "ngx-extractor": "ngx-extractor",
"stylelint": "stylelint" "stylelint": "stylelint"
}, },
"workspaces": [
"../packages/*"
],
"typings": "*.d.ts", "typings": "*.d.ts",
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^16.0.2", "@angular-devkit/build-angular": "^16.0.2",
@ -57,6 +60,8 @@
"@peertube/maildev": "^1.2.0", "@peertube/maildev": "^1.2.0",
"@peertube/p2p-media-loader-core": "^1.0.14", "@peertube/p2p-media-loader-core": "^1.0.14",
"@peertube/p2p-media-loader-hlsjs": "^1.0.14", "@peertube/p2p-media-loader-hlsjs": "^1.0.14",
"@peertube/peertube-core-utils": "*",
"@peertube/peertube-models": "*",
"@peertube/videojs-contextmenu": "^5.5.0", "@peertube/videojs-contextmenu": "^5.5.0",
"@peertube/xliffmerge": "^2.0.3", "@peertube/xliffmerge": "^2.0.3",
"@popperjs/core": "^2.11.5", "@popperjs/core": "^2.11.5",
@ -86,7 +91,7 @@
"buffer": "^6.0.3", "buffer": "^6.0.3",
"chart.js": "^4.3.0", "chart.js": "^4.3.0",
"chartjs-plugin-zoom": "~2.0.1", "chartjs-plugin-zoom": "~2.0.1",
"chromedriver": "^113.0.0", "chromedriver": "^115.0.1",
"core-js": "^3.22.8", "core-js": "^3.22.8",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"debug": "^4.3.1", "debug": "^4.3.1",
@ -122,6 +127,7 @@
"stylelint": "^15.1.0", "stylelint": "^15.1.0",
"stylelint-config-sass-guidelines": "^10.0.0", "stylelint-config-sass-guidelines": "^10.0.0",
"ts-loader": "^9.3.0", "ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"typescript": "~4.9.5", "typescript": "~4.9.5",
"video.js": "^7.19.2", "video.js": "^7.19.2",

View file

@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { ComponentPagination, hasMoreItems, Notifier, RestService, ServerService } from '@app/core' import { ComponentPagination, hasMoreItems, Notifier, RestService, ServerService } from '@app/core'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { Actor } from '@shared/models/actors' import { Actor } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-about-follows', selector: 'my-about-follows',

View file

@ -3,8 +3,8 @@ import { AfterViewChecked, Component, ElementRef, OnInit, ViewChild } from '@ang
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { Notifier, ServerService } from '@app/core' import { Notifier, ServerService } from '@app/core'
import { AboutHTML } from '@app/shared/shared-instance' import { AboutHTML } from '@app/shared/shared-instance'
import { HTMLServerConfig, ServerStats } from '@peertube/peertube-models'
import { copyToClipboard } from '@root-helpers/utils' import { copyToClipboard } from '@root-helpers/utils'
import { HTMLServerConfig, ServerStats } from '@shared/models/server'
import { ResolverData } from './about-instance.resolver' import { ResolverData } from './about-instance.resolver'
import { ContactAdminModalComponent } from './contact-admin-modal.component' import { ContactAdminModalComponent } from './contact-admin-modal.component'

View file

@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'
import { ServerService } from '@app/core' import { ServerService } from '@app/core'
import { CustomMarkupService } from '@app/shared/shared-custom-markup' import { CustomMarkupService } from '@app/shared/shared-custom-markup'
import { AboutHTML, InstanceService } from '@app/shared/shared-instance' import { AboutHTML, InstanceService } from '@app/shared/shared-instance'
import { About, ServerStats } from '@shared/models/server' import { About, ServerStats } from '@peertube/peertube-models'
export type ResolverData = { export type ResolverData = {
serverStats: ServerStats serverStats: ServerStats

View file

@ -11,7 +11,7 @@ import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { InstanceService } from '@app/shared/shared-instance' import { InstanceService } from '@app/shared/shared-instance'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { HTMLServerConfig, HttpStatusCode } from '@shared/models' import { HTMLServerConfig, HttpStatusCode } from '@peertube/peertube-models'
type Prefill = { type Prefill = {
subject?: string subject?: string

View file

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { ServerStats } from '@shared/models/server' import { ServerStats } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-instance-statistics', selector: 'my-instance-statistics',

View file

@ -5,7 +5,7 @@ import { ComponentPagination, hasMoreItems, MarkdownService, User, UserService }
import { SimpleMemoize } from '@app/helpers' import { SimpleMemoize } from '@app/helpers'
import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' import { Account, AccountService, Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature' import { MiniatureDisplayOptions } from '@app/shared/shared-video-miniature'
import { NSFWPolicyType, VideoSortField } from '@shared/models' import { NSFWPolicyType, VideoSortField } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-account-video-channels', selector: 'my-account-video-channels',

View file

@ -4,7 +4,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPaginationLight, DisableForReuseHook, ScreenService } from '@app/core' import { ComponentPaginationLight, DisableForReuseHook, ScreenService } from '@app/core'
import { Account, AccountService, VideoService } from '@app/shared/shared-main' import { Account, AccountService, VideoService } from '@app/shared/shared-main'
import { VideoFilters } from '@app/shared/shared-video-miniature' import { VideoFilters } from '@app/shared/shared-video-miniature'
import { VideoSortField } from '@shared/models' import { VideoSortField } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-account-videos', selector: 'my-account-videos',

View file

@ -13,7 +13,7 @@ import {
VideoService VideoService
} from '@app/shared/shared-main' } from '@app/shared/shared-main'
import { AccountReportComponent, BlocklistService } from '@app/shared/shared-moderation' import { AccountReportComponent, BlocklistService } from '@app/shared/shared-moderation'
import { HttpStatusCode, User, UserRight } from '@shared/models' import { HttpStatusCode, User, UserRight } from '@peertube/peertube-models'
@Component({ @Component({
templateUrl: './accounts.component.html', templateUrl: './accounts.component.html',

View file

@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'
import { AuthService, ScreenService, ServerService } from '@app/core' import { AuthService, ScreenService, ServerService } from '@app/core'
import { ListOverflowItem } from '@app/shared/shared-main' import { ListOverflowItem } from '@app/shared/shared-main'
import { TopMenuDropdownParam } from '@app/shared/shared-main/misc/top-menu-dropdown.component' import { TopMenuDropdownParam } from '@app/shared/shared-main/misc/top-menu-dropdown.component'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
@Component({ @Component({
templateUrl: './admin.component.html', templateUrl: './admin.component.html',

View file

@ -1,7 +1,7 @@
import { Routes } from '@angular/router' import { Routes } from '@angular/router'
import { EditCustomConfigComponent } from '@app/+admin/config/edit-custom-config' import { EditCustomConfigComponent } from '@app/+admin/config/edit-custom-config'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
export const ConfigRoutes: Routes = [ export const ConfigRoutes: Routes = [
{ {

View file

@ -3,7 +3,7 @@ import { SelectOptionsItem } from 'src/types/select-options-item.model'
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core' import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
import { FormGroup } from '@angular/forms' import { FormGroup } from '@angular/forms'
import { MenuService, ThemeService } from '@app/core' import { MenuService, ThemeService } from '@app/core'
import { HTMLServerConfig } from '@shared/models' import { HTMLServerConfig } from '@peertube/peertube-models'
import { ConfigService } from '../shared/config.service' import { ConfigService } from '../shared/config.service'
@Component({ @Component({

View file

@ -27,7 +27,7 @@ import {
import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators' import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators'
import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { CustomPageService } from '@app/shared/shared-main/custom-page' import { CustomPageService } from '@app/shared/shared-main/custom-page'
import { CustomConfig, CustomPage, HTMLServerConfig } from '@shared/models' import { CustomConfig, CustomPage, HTMLServerConfig } from '@peertube/peertube-models'
import { EditConfigurationService } from './edit-configuration.service' import { EditConfigurationService } from './edit-configuration.service'
type ComponentCustomConfig = CustomConfig & { type ComponentCustomConfig = CustomConfig & {

View file

@ -2,7 +2,7 @@
import { SelectOptionsItem } from 'src/types/select-options-item.model' import { SelectOptionsItem } from 'src/types/select-options-item.model'
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core' import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
import { FormGroup } from '@angular/forms' import { FormGroup } from '@angular/forms'
import { HTMLServerConfig } from '@shared/models' import { HTMLServerConfig } from '@peertube/peertube-models'
import { ConfigService } from '../shared/config.service' import { ConfigService } from '../shared/config.service'
import { EditConfigurationService, ResolutionOption } from './edit-configuration.service' import { EditConfigurationService, ResolutionOption } from './edit-configuration.service'

View file

@ -2,7 +2,7 @@
import { SelectOptionsItem } from 'src/types/select-options-item.model' import { SelectOptionsItem } from 'src/types/select-options-item.model'
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core' import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
import { FormGroup } from '@angular/forms' import { FormGroup } from '@angular/forms'
import { HTMLServerConfig } from '@shared/models' import { HTMLServerConfig } from '@peertube/peertube-models'
import { ConfigService } from '../shared/config.service' import { ConfigService } from '../shared/config.service'
import { EditConfigurationService, ResolutionOption } from './edit-configuration.service' import { EditConfigurationService, ResolutionOption } from './edit-configuration.service'

View file

@ -2,7 +2,7 @@ import { catchError } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor } from '@app/core' import { RestExtractor } from '@app/core'
import { CustomConfig } from '@shared/models' import { CustomConfig } from '@peertube/peertube-models'
import { SelectOptionsItem } from '../../../../types/select-options-item.model' import { SelectOptionsItem } from '../../../../types/select-options-item.model'
import { environment } from '../../../../environments/environment' import { environment } from '../../../../environments/environment'

View file

@ -5,7 +5,7 @@ import { formatICU } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { DropdownAction } from '@app/shared/shared-main' import { DropdownAction } from '@app/shared/shared-main'
import { ActorFollow } from '@shared/models' import { ActorFollow } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-followers-list', selector: 'my-followers-list',

View file

@ -3,7 +3,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'
import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { ActorFollow } from '@shared/models' import { ActorFollow } from '@peertube/peertube-models'
import { FollowModalComponent } from './follow-modal.component' import { FollowModalComponent } from './follow-modal.component'
import { DropdownAction } from '@app/shared/shared-main' import { DropdownAction } from '@app/shared/shared-main'
import { formatICU } from '@app/helpers' import { formatICU } from '@app/helpers'

View file

@ -1,7 +1,7 @@
import { Routes } from '@angular/router' import { Routes } from '@angular/router'
import { VideoRedundanciesListComponent } from '@app/+admin/follows/video-redundancies-list' import { VideoRedundanciesListComponent } from '@app/+admin/follows/video-redundancies-list'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
import { FollowersListComponent } from './followers-list' import { FollowersListComponent } from './followers-list'
import { FollowingListComponent } from './following-list/following-list.component' import { FollowingListComponent } from './following-list/following-list.component'

View file

@ -3,9 +3,8 @@ import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' import { ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
import { BytesPipe, RedundancyService } from '@app/shared/shared-main' import { BytesPipe, RedundancyService } from '@app/shared/shared-main'
import { VideoRedundanciesTarget, VideoRedundancy, VideosRedundancyStats } from '@peertube/peertube-models'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
import { VideosRedundancyStats } from '@shared/models/server'
@Component({ @Component({
selector: 'my-video-redundancies-list', selector: 'my-video-redundancies-list',

View file

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@shared/models' import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-video-redundancy-information', selector: 'my-video-redundancy-information',

View file

@ -3,7 +3,7 @@ import { AbuseListComponent } from '@app/+admin/moderation/abuse-list'
import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list' import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
import { RegistrationListComponent } from './registration-list' import { RegistrationListComponent } from './registration-list'
export const ModerationRoutes: Routes = [ export const ModerationRoutes: Routes = [

View file

@ -4,8 +4,8 @@ import { catchError, concatMap, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils' import { arrayify } from '@peertube/peertube-core-utils'
import { ResultList, UserRegistration, UserRegistrationUpdateState } from '@shared/models' import { ResultList, UserRegistration, UserRegistrationUpdateState } from '@peertube/peertube-models'
import { environment } from '../../../../environments/environment' import { environment } from '../../../../environments/environment'
@Injectable() @Injectable()

View file

@ -3,7 +3,7 @@ import { Notifier, ServerService } from '@app/core'
import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { UserRegistration } from '@shared/models' import { UserRegistration } from '@peertube/peertube-models'
import { AdminRegistrationService } from './admin-registration.service' import { AdminRegistrationService } from './admin-registration.service'
import { REGISTRATION_MODERATION_RESPONSE_VALIDATOR } from './process-registration-validators' import { REGISTRATION_MODERATION_RESPONSE_VALIDATOR } from './process-registration-validators'

View file

@ -5,7 +5,7 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, S
import { formatICU } from '@app/helpers' import { formatICU } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction } from '@app/shared/shared-main' import { DropdownAction } from '@app/shared/shared-main'
import { UserRegistration, UserRegistrationState } from '@shared/models' import { UserRegistration, UserRegistrationState } from '@peertube/peertube-models'
import { AdminRegistrationService } from './admin-registration.service' import { AdminRegistrationService } from './admin-registration.service'
import { ProcessRegistrationModalComponent } from './process-registration-modal.component' import { ProcessRegistrationModalComponent } from './process-registration-modal.component'

View file

@ -7,9 +7,9 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, S
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction, VideoService } from '@app/shared/shared-main' import { DropdownAction, VideoService } from '@app/shared/shared-main'
import { VideoBlockService } from '@app/shared/shared-moderation' import { VideoBlockService } from '@app/shared/shared-moderation'
import { buildVideoEmbedLink, decorateVideoLink } from '@peertube/peertube-core-utils'
import { VideoBlacklist, VideoBlacklistType, VideoBlacklistType_Type } from '@peertube/peertube-models'
import { buildVideoOrPlaylistEmbed } from '@root-helpers/video' import { buildVideoOrPlaylistEmbed } from '@root-helpers/video'
import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@Component({ @Component({
selector: 'my-video-block-list', selector: 'my-video-block-list',
@ -21,7 +21,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
totalRecords = 0 totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 } sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
blocklistTypeFilter: VideoBlacklistType = undefined blocklistTypeFilter: VideoBlacklistType_Type
videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = [] videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = []

View file

@ -6,7 +6,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction } from '@app/shared/shared-main' import { DropdownAction } from '@app/shared/shared-main'
import { BulkService } from '@app/shared/shared-moderation' import { BulkService } from '@app/shared/shared-moderation'
import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
import { FeedFormat, UserRight } from '@shared/models' import { FeedFormat, UserRight } from '@peertube/peertube-models'
import { formatICU } from '@app/helpers' import { formatICU } from '@app/helpers'
@Component({ @Component({

View file

@ -1,6 +1,6 @@
import { Routes } from '@angular/router' import { Routes } from '@angular/router'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
import { VideoCommentListComponent } from './video-comment-list.component' import { VideoCommentListComponent } from './video-comment-list.component'
export const commentRoutes: Routes = [ export const commentRoutes: Routes = [

View file

@ -14,7 +14,7 @@ import {
} from '@app/shared/form-validators/user-validators' } from '@app/shared/form-validators/user-validators'
import { FormReactiveService } from '@app/shared/shared-forms' import { FormReactiveService } from '@app/shared/shared-forms'
import { UserAdminService } from '@app/shared/shared-users' import { UserAdminService } from '@app/shared/shared-users'
import { UserCreate, UserRole } from '@shared/models' import { UserCreate, UserRole } from '@peertube/peertube-models'
import { UserEdit } from './user-edit' import { UserEdit } from './user-edit'
@Component({ @Component({

View file

@ -2,9 +2,8 @@ import { Directive, OnInit } from '@angular/core'
import { ConfigService } from '@app/+admin/config/shared/config.service' import { ConfigService } from '@app/+admin/config/shared/config.service'
import { AuthService, ScreenService, ServerService, User } from '@app/core' import { AuthService, ScreenService, ServerService, User } from '@app/core'
import { FormReactive } from '@app/shared/shared-forms' import { FormReactive } from '@app/shared/shared-forms'
import { peertubeTranslate } from '@shared/core-utils' import { peertubeTranslate, USER_ROLE_LABELS } from '@peertube/peertube-core-utils'
import { USER_ROLE_LABELS } from '@shared/core-utils/users' import { HTMLServerConfig, UserAdminFlag, UserRole } from '@peertube/peertube-models'
import { HTMLServerConfig, UserAdminFlag, UserRole } from '@shared/models'
import { SelectOptionsItem } from '../../../../../types/select-options-item.model' import { SelectOptionsItem } from '../../../../../types/select-options-item.model'
@Directive() @Directive()

View file

@ -3,7 +3,7 @@ import { Notifier } from '@app/core'
import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators'
import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { UserAdminService } from '@app/shared/shared-users' import { UserAdminService } from '@app/shared/shared-users'
import { UserUpdate } from '@shared/models' import { UserUpdate } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-user-password', selector: 'my-user-password',

View file

@ -11,7 +11,7 @@ import {
} from '@app/shared/form-validators/user-validators' } from '@app/shared/form-validators/user-validators'
import { FormReactiveService } from '@app/shared/shared-forms' import { FormReactiveService } from '@app/shared/shared-forms'
import { TwoFactorService, UserAdminService } from '@app/shared/shared-users' import { TwoFactorService, UserAdminService } from '@app/shared/shared-users'
import { User as UserType, UserAdminFlag, UserRole, UserUpdate } from '@shared/models' import { User as UserType, UserAdminFlag, UserRole, UserUpdate } from '@peertube/peertube-models'
import { UserEdit } from './user-edit' import { UserEdit } from './user-edit'
@Component({ @Component({

View file

@ -7,8 +7,8 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { Actor, DropdownAction } from '@app/shared/shared-main' import { Actor, DropdownAction } from '@app/shared/shared-main'
import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
import { UserAdminService } from '@app/shared/shared-users' import { UserAdminService } from '@app/shared/shared-users'
import { User, UserRole, UserRoleType } from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
import { User, UserRole } from '@shared/models'
type UserForList = User & { type UserForList = User & {
rawVideoQuota: number rawVideoQuota: number
@ -166,7 +166,7 @@ export class UserListComponent extends RestTable <User> implements OnInit {
return 'UserListComponent' return 'UserListComponent'
} }
getRoleClass (role: UserRole) { getRoleClass (role: UserRoleType) {
switch (role) { switch (role) {
case UserRole.ADMINISTRATOR: case UserRole.ADMINISTRATOR:
return 'badge-purple' return 'badge-purple'

View file

@ -1,6 +1,6 @@
import { Routes } from '@angular/router' import { Routes } from '@angular/router'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
import { UserCreateComponent, UserUpdateComponent } from './user-edit' import { UserCreateComponent, UserUpdateComponent } from './user-edit'
import { UserListComponent } from './user-list' import { UserListComponent } from './user-list'

View file

@ -5,8 +5,8 @@ import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main' import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main'
import { ResultList, VideoInclude, VideoPrivacy } from '@shared/models' import { ResultList, VideoInclude, VideoPrivacy } from '@peertube/peertube-models'
import { getAllPrivacies } from '@shared/core-utils' import { getAllPrivacies } from '@peertube/peertube-core-utils'
@Injectable() @Injectable()
export class VideoAdminService { export class VideoAdminService {

View file

@ -8,8 +8,8 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature' import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
import { getAllFiles } from '@shared/core-utils' import { getAllFiles } from '@peertube/peertube-core-utils'
import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models' import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@peertube/peertube-models'
import { VideoAdminService } from './video-admin.service' import { VideoAdminService } from './video-admin.service'
@Component({ @Component({

View file

@ -1,6 +1,6 @@
import { Routes } from '@angular/router' import { Routes } from '@angular/router'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
import { VideoListComponent } from './video-list.component' import { VideoListComponent } from './video-list.component'
export const videosRoutes: Routes = [ export const videosRoutes: Routes = [

View file

@ -4,8 +4,8 @@ import { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core' import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core'
import { PluginService } from '@app/core/plugins/plugin.service' import { PluginService } from '@app/core/plugins/plugin.service'
import { compareSemVer } from '@shared/core-utils' import { compareSemVer } from '@peertube/peertube-core-utils'
import { PeerTubePlugin, PluginType } from '@shared/models' import { PeerTubePlugin, PluginType, PluginType_Type } from '@peertube/peertube-models'
@Component({ @Component({
selector: 'my-plugin-list-installed', selector: 'my-plugin-list-installed',
@ -13,7 +13,7 @@ import { PeerTubePlugin, PluginType } from '@shared/models'
styleUrls: [ './plugin-list-installed.component.scss' ] styleUrls: [ './plugin-list-installed.component.scss' ]
}) })
export class PluginListInstalledComponent implements OnInit { export class PluginListInstalledComponent implements OnInit {
pluginType: PluginType pluginType: PluginType_Type
pagination: ComponentPagination = { pagination: ComponentPagination = {
currentPage: 1, currentPage: 1,
@ -48,7 +48,7 @@ export class PluginListInstalledComponent implements OnInit {
this.route.queryParams.subscribe(query => { this.route.queryParams.subscribe(query => {
if (!query['pluginType']) return if (!query['pluginType']) return
this.pluginType = parseInt(query['pluginType'], 10) this.pluginType = parseInt(query['pluginType'], 10) as PluginType_Type
this.reloadPlugins() this.reloadPlugins()
}) })

View file

@ -4,8 +4,8 @@ import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core' import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
import { PeerTubePluginIndex, PluginType, PluginType_Type } from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger' import { logger } from '@root-helpers/logger'
import { PeerTubePluginIndex, PluginType } from '@shared/models'
@Component({ @Component({
selector: 'my-plugin-search', selector: 'my-plugin-search',
@ -13,7 +13,7 @@ import { PeerTubePluginIndex, PluginType } from '@shared/models'
styleUrls: [ './plugin-search.component.scss' ] styleUrls: [ './plugin-search.component.scss' ]
}) })
export class PluginSearchComponent implements OnInit { export class PluginSearchComponent implements OnInit {
pluginType: PluginType pluginType: PluginType_Type
pagination: ComponentPagination = { pagination: ComponentPagination = {
currentPage: 1, currentPage: 1,
@ -53,7 +53,7 @@ export class PluginSearchComponent implements OnInit {
this.route.queryParams.subscribe(query => { this.route.queryParams.subscribe(query => {
if (!query['pluginType']) return if (!query['pluginType']) return
this.pluginType = parseInt(query['pluginType'], 10) this.pluginType = parseInt(query['pluginType'], 10) as PluginType_Type
this.search = query['search'] || '' this.search = query['search'] || ''
this.reloadPlugins() this.reloadPlugins()

View file

@ -5,7 +5,7 @@ import { ActivatedRoute } from '@angular/router'
import { HooksService, Notifier, PluginService } from '@app/core' import { HooksService, Notifier, PluginService } from '@app/core'
import { BuildFormArgument } from '@app/shared/form-validators' import { BuildFormArgument } from '@app/shared/form-validators'
import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { PeerTubePlugin, RegisterServerSettingOptions } from '@shared/models' import { PeerTubePlugin, RegisterServerSettingOptions } from '@peertube/peertube-models'
import { PluginApiService } from '../shared/plugin-api.service' import { PluginApiService } from '../shared/plugin-api.service'
@Component({ @Component({

View file

@ -3,7 +3,7 @@ import { PluginListInstalledComponent } from '@app/+admin/plugins/plugin-list-in
import { PluginSearchComponent } from '@app/+admin/plugins/plugin-search/plugin-search.component' import { PluginSearchComponent } from '@app/+admin/plugins/plugin-search/plugin-search.component'
import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component' import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component'
import { UserRightGuard } from '@app/core' import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models' import { UserRight } from '@peertube/peertube-models'
export const PluginsRoutes: Routes = [ export const PluginsRoutes: Routes = [
{ {

View file

@ -9,9 +9,10 @@ import {
PeerTubePlugin, PeerTubePlugin,
PeerTubePluginIndex, PeerTubePluginIndex,
PluginType, PluginType,
PluginType_Type,
RegisteredServerSettings, RegisteredServerSettings,
ResultList ResultList
} from '@shared/models' } from '@peertube/peertube-models'
import { environment } from '../../../../environments/environment' import { environment } from '../../../../environments/environment'
@Injectable() @Injectable()
@ -25,7 +26,7 @@ export class PluginApiService {
private pluginService: PluginService private pluginService: PluginService
) { } ) { }
getPluginTypeLabel (type: PluginType) { getPluginTypeLabel (type: PluginType_Type) {
if (type === PluginType.PLUGIN) { if (type === PluginType.PLUGIN) {
return $localize`plugin` return $localize`plugin`
} }
@ -34,7 +35,7 @@ export class PluginApiService {
} }
getPlugins ( getPlugins (
pluginType: PluginType, pluginType: PluginType_Type,
componentPagination: ComponentPagination, componentPagination: ComponentPagination,
sort: string sort: string
) { ) {
@ -49,7 +50,7 @@ export class PluginApiService {
} }
searchAvailablePlugins ( searchAvailablePlugins (
pluginType: PluginType, pluginType: PluginType_Type,
componentPagination: ComponentPagination, componentPagination: ComponentPagination,
sort: string, sort: string,
search?: string search?: string
@ -73,7 +74,7 @@ export class PluginApiService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
getPluginRegisteredSettings (pluginName: string, pluginType: PluginType) { getPluginRegisteredSettings (pluginName: string, pluginType: PluginType_Type) {
const npmName = this.pluginService.nameToNpmName(pluginName, pluginType) const npmName = this.pluginService.nameToNpmName(pluginName, pluginType)
const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/registered-settings' const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/registered-settings'
@ -83,7 +84,7 @@ export class PluginApiService {
) )
} }
updatePluginSettings (pluginName: string, pluginType: PluginType, settings: any) { updatePluginSettings (pluginName: string, pluginType: PluginType_Type, settings: any) {
const npmName = this.pluginService.nameToNpmName(pluginName, pluginType) const npmName = this.pluginService.nameToNpmName(pluginName, pluginType)
const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/settings' const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/settings'
@ -91,7 +92,7 @@ export class PluginApiService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
uninstall (pluginName: string, pluginType: PluginType) { uninstall (pluginName: string, pluginType: PluginType_Type) {
const body: ManagePlugin = { const body: ManagePlugin = {
npmName: this.pluginService.nameToNpmName(pluginName, pluginType) npmName: this.pluginService.nameToNpmName(pluginName, pluginType)
} }
@ -100,7 +101,7 @@ export class PluginApiService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
update (pluginName: string, pluginType: PluginType) { update (pluginName: string, pluginType: PluginType_Type) {
const body: ManagePlugin = { const body: ManagePlugin = {
npmName: this.pluginService.nameToNpmName(pluginName, pluginType) npmName: this.pluginService.nameToNpmName(pluginName, pluginType)
} }
@ -118,7 +119,7 @@ export class PluginApiService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
getPluginOrThemeHref (type: PluginType, name: string) { getPluginOrThemeHref (type: PluginType_Type, name: string) {
const typeString = type === PluginType.PLUGIN const typeString = type === PluginType.PLUGIN
? 'plugin' ? 'plugin'
: 'theme' : 'theme'

View file

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { PeerTubePlugin, PeerTubePluginIndex, PluginType } from '@shared/models' import { PeerTubePlugin, PeerTubePluginIndex, PluginType_Type } from '@peertube/peertube-models'
import { PluginApiService } from './plugin-api.service' import { PluginApiService } from './plugin-api.service'
@Component({ @Component({
@ -11,7 +11,7 @@ import { PluginApiService } from './plugin-api.service'
export class PluginCardComponent { export class PluginCardComponent {
@Input() plugin: PeerTubePluginIndex | PeerTubePlugin @Input() plugin: PeerTubePluginIndex | PeerTubePlugin
@Input() version: string @Input() version: string
@Input() pluginType: PluginType @Input() pluginType: PluginType_Type
constructor ( constructor (
private pluginApiService: PluginApiService private pluginApiService: PluginApiService

Some files were not shown because too many files have changed in this diff Show more