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",
"root": true,
"rules": {
"eol-last": [
"error",
@ -126,18 +127,20 @@
]
},
"ignorePatterns": [
"node_modules/",
"server/tests/fixtures"
"node_modules",
"packages/tests/fixtures",
"apps/**/dist",
"packages/**/dist",
"server/dist",
"packages/types-generator/tests",
"*.js",
"/client",
"/dist"
],
"parserOptions": {
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
"project": [
"./tsconfig.json",
"./shared/tsconfig.json",
"./scripts/tsconfig.json",
"./server/tsconfig.json",
"./server/tools/tsconfig.json",
"./packages/peertube-runner/tsconfig.json"
]
"./tsconfig.eslint.json"
],
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true
}
}

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,
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.
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.
### User documentation
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:
* Routes are defined in [/server/controllers/](https://github.com/Chocobozzz/PeerTube/tree/develop/server/controllers) directory
* Parameters validators are defined in [/server/middlewares/validators](https://github.com/Chocobozzz/PeerTube/tree/develop/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
* Routes are defined in [/server/server/controllers/](https://github.com/Chocobozzz/PeerTube/tree/develop/server/server/controllers) 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 [/packages/models](https://github.com/Chocobozzz/PeerTube/tree/develop/packages/models) directory
## Improve the website
@ -242,15 +254,6 @@ To test emails with PeerTube:
* 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`
### 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
PeerTube can be configured using environment variables.

View file

@ -32,4 +32,12 @@ runs:
- name: Install peertube runner dependencies
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
run: |
node dist/scripts/benchmark.js -o benchmark.json
npm run benchmark-server -- -o benchmark.json
- name: Display result
run: |

View file

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

View file

@ -36,12 +36,12 @@ jobs:
run: |
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"
./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
if: github.event_name != 'pull_request'
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
if: github.event_name != 'pull_request'

16
.gitignore vendored
View file

@ -1,8 +1,8 @@
# NPM instalation
/node_modules/
/server/tools/node_modules
node_modules
*npm-debug.log
yarn-error.log
.yarn
# Testing
/test1/
@ -11,8 +11,8 @@ yarn-error.log
/test4/
/test5/
/test6/
/server/tests/fixtures/video_high_bitrate_1080p.mp4
/server/tests/fixtures/video_59fps.mp4
/packages/tests/fixtures/video_high_bitrate_1080p.mp4
/packages/tests/fixtures/video_59fps.mp4
# Production
/storage
@ -49,12 +49,14 @@ yarn-error.log
/*.tar.xz
/*.asc
*.DS_Store
/server/tools/import-mediacore.ts
/docker-volume/
/init.mp4
# TypeScript
*.tsbuildinfo
# Packages
/packages/types/dist/
# EsLint
.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
import { CommandOptions, program } from 'commander'
import { getSettings, version } from './shared'
import { Command } from '@commander-js/extra-typings'
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
.version(version, '-v, --version')
.usage('[command] [options]')
/* Subcommands automatically loaded in the directory and beginning by peertube-* */
program
.command('auth [action]', 'register your accounts on remote instances to use them with other commands')
.command('upload', 'upload a video').alias('up')
.command('import-videos', 'import a video from a streaming platform').alias('import')
.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')
program.addCommand(defineAuthProgram())
program.addCommand(defineUploadProgram())
program.addCommand(defineRedundancyProgram())
program.addCommand(definePluginsProgram())
program.addCommand(defineGetAccessProgram())
// help on no command
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 { join } from 'path'
import { createLogger, format, transports } from 'winston'
import { getAppNumber, isTestInstance } from '@server/helpers/core-utils'
import { loadLanguages } from '@server/initializers/constants'
import { root } from '@shared/core-utils'
import { UserRole, VideoPrivacy } from '@shared/models'
import { PeerTubeServer } from '@shared/server-commands'
import { UserRole } from '@peertube/peertube-models'
import { getAppNumber, isTestInstance, root } from '@peertube/peertube-node-utils'
import { PeerTubeServer } from '@peertube/peertube-server-commands'
export type CommonProgramOptions = {
url?: string
username?: string
password?: string
}
let configName = 'PeerTube/CLI'
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) {
const token = await server.login.getAccessToken(username, password)
@ -32,13 +36,13 @@ interface Settings {
default: number
}
async function getSettings (): Promise<Settings> {
const defaultSettings = {
async function getSettings () {
const defaultSettings: Settings = {
remotes: [],
default: -1
}
const data = await config.read()
const data = await config.read() as Promise<Settings>
return Object.keys(data).length === 0
? defaultSettings
@ -46,10 +50,8 @@ async function getSettings (): Promise<Settings> {
}
async function getNetrc () {
const Netrc = require('netrc-parser').Netrc
const netrc = isTestInstance()
? new Netrc(join(root(), 'test' + getAppNumber(), 'netrc'))
? new Netrc(join(root(import.meta.url), 'test' + getAppNumber(), 'netrc'))
: new Netrc()
await netrc.load()
@ -66,11 +68,10 @@ function deleteSettings () {
}
function getRemoteObjectOrDie (
program: Command,
options: CommonProgramOptions,
settings: Settings,
netrc: Netrc
): { url: string, username: string, password: string } {
const options = program.opts()
function exitIfNoOptions (optionNames: string[], errorPrefix: string = '') {
let exit = false
@ -119,85 +120,18 @@ function getRemoteObjectOrDie (
return { url, username, password }
}
function buildCommonVideoOptions (command: Command) {
function list (val) {
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')
function listOptions (val: any) {
return val.split(',')
}
async function buildVideoAttributesFromCommander (server: PeerTubeServer, command: Command, defaultAttributes: any = {}) {
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) {
function getServerCredentials (options: CommonProgramOptions) {
return Promise.all([ getSettings(), getNetrc() ])
.then(([ settings, netrc ]) => {
return getRemoteObjectOrDie(program, settings, netrc)
return getRemoteObjectOrDie(options, settings, netrc)
})
}
function buildServer (url: string) {
loadLanguages()
return new PeerTubeServer({ url })
}
@ -253,8 +187,7 @@ export {
getServerCredentials,
buildCommonVideoOptions,
buildVideoAttributesFromCommander,
listOptions,
getAdminTokenOrDie,
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":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3"
integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==
dependencies:
"@babel/highlight" "^7.16.7"
"@babel/highlight" "^7.22.10"
chalk "^2.4.2"
"@babel/helper-validator-identifier@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
"@babel/helper-validator-identifier@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
"@babel/highlight@^7.16.7":
version "7.16.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
"@babel/highlight@^7.22.10":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7"
integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==
dependencies:
"@babel/helper-validator-identifier" "^7.16.7"
chalk "^2.0.0"
"@babel/helper-validator-identifier" "^7.22.5"
chalk "^2.4.2"
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:
version "5.0.1"
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"
application-config-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f"
integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8=
version "0.1.1"
resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.1.tgz#8b5ac64ff6afdd9bd70ce69f6f64b6998f5f756e"
integrity sha512-zy9cHePtMP0YhwG+CfHm0bgwdnga2X3gZexpdCwEj//dpb+TKajtiC8REEUJUSq6Ab4f9cgNy2l8ObXzCXFkEw==
application-config@^2.0.0:
version "2.0.0"
@ -49,7 +55,7 @@ application-config@^2.0.0:
load-json-file "^6.2.0"
write-json-file "^4.2.0"
chalk@^2.0.0:
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@ -59,13 +65,13 @@ chalk@^2.0.0:
supports-color "^5.3.0"
cli-table3@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8"
integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==
version "0.6.3"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2"
integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==
dependencies:
string-width "^4.2.0"
optionalDependencies:
colors "1.4.0"
"@colors/colors" "1.5.0"
color-convert@^1.9.0:
version "1.9.3"
@ -77,12 +83,7 @@ color-convert@^1.9.0:
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
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==
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
cross-spawn@^6.0.0:
version "6.0.5"
@ -122,7 +123,7 @@ error-ex@^1.3.1:
escape-string-regexp@^1.0.5:
version "1.0.5"
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:
version "0.10.0"
@ -140,27 +141,27 @@ execa@^0.10.0:
get-stream@^3.0.0:
version "3.0.0"
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:
version "4.2.9"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-flag@^3.0.0:
version "3.0.0"
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:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
is-arrayish@^0.2.1:
version "0.2.1"
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:
version "3.0.0"
@ -175,17 +176,17 @@ is-plain-obj@^2.0.0:
is-stream@^1.1.0:
version "1.1.0"
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
isexe@^2.0.0:
version "2.0.0"
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:
version "4.0.0"
@ -240,14 +241,14 @@ nice-try@^1.0.4:
npm-run-path@^2.0.0:
version "2.0.2"
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:
path-key "^2.0.0"
p-finally@^1.0.0:
version "1.0.0"
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:
version "5.2.0"
@ -262,29 +263,29 @@ parse-json@^5.0.0:
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
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:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@^6.0.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
shebang-command@^1.2.0:
version "1.2.0"
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:
shebang-regex "^1.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
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:
version "3.0.7"
@ -322,7 +323,7 @@ strip-bom@^4.0.0:
strip-eof@^1.0.0:
version "1.0.0"
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:
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",
"version": "0.0.5",
"type": "module",
"main": "dist/peertube-runner.js",
"bin": "dist/peertube-runner.js",
"engines": {
"node": ">=16.x"
},
"license": "AGPL-3.0",
"dependencies": {},
"devDependencies": {
"@commander-js/extra-typings": "^10.0.3",
"@iarna/toml": "^2.2.5",
"env-paths": "^3.0.0",
"esbuild": "^0.17.15",
"net-ipc": "^2.0.1",
"pino": "^8.11.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
import { Command, InvalidArgumentError } from '@commander-js/extra-typings'
import { listRegistered, registerRunner, unregisterRunner } from './register'
import { RunnerServer } from './server'
import { ConfigManager, logger } from './shared'
const packageJSON = require('./package.json')
import { listRegistered, registerRunner, unregisterRunner } from './register/index.js'
import { RunnerServer } from './server/index.js'
import { ConfigManager, logger } from './shared/index.js'
const program = new Command()
.version(packageJSON.version)
.version(process.env.PACKAGE_VERSION)
.option(
'--id <id>',
'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: {
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 {
RunnerJobLiveRTMPHLSTranscodingPayload,
RunnerJobStudioTranscodingPayload,
RunnerJobVODAudioMergeTranscodingPayload,
RunnerJobVODHLSTranscodingPayload,
RunnerJobVODWebVideoTranscodingPayload
} from '@shared/models'
import { processAudioMergeTranscoding, processHLSTranscoding, ProcessOptions, processWebVideoTranscoding } from './shared'
import { ProcessLiveRTMPHLSTranscoding } from './shared/process-live'
import { processStudioTranscoding } from './shared/process-studio'
} from '@peertube/peertube-models'
import { logger } from '../../shared/index.js'
import { processAudioMergeTranscoding, processHLSTranscoding, ProcessOptions, processWebVideoTranscoding } from './shared/index.js'
import { ProcessLiveRTMPHLSTranscoding } from './shared/process-live.js'
import { processStudioTranscoding } from './shared/process-studio.js'
export async function processJob (options: ProcessOptions) {
const { server, job } = options

View file

@ -1,11 +1,11 @@
import { remove } from 'fs-extra'
import { ConfigManager, downloadFile, logger } from 'packages/peertube-runner/shared'
import { remove } from 'fs-extra/esm'
import { join } from 'path'
import { buildUUID } from '@shared/extra-utils'
import { FFmpegEdition, FFmpegLive, FFmpegVOD, getDefaultAvailableEncoders, getDefaultEncodersToTry } from '@shared/ffmpeg'
import { RunnerJob, RunnerJobPayload } from '@shared/models'
import { PeerTubeServer } from '@shared/server-commands'
import { getTranscodingLogger } from './transcoding-logger'
import { FFmpegEdition, FFmpegLive, FFmpegVOD, getDefaultAvailableEncoders, getDefaultEncodersToTry } from '@peertube/peertube-ffmpeg'
import { RunnerJob, RunnerJobPayload } from '@peertube/peertube-models'
import { buildUUID } from '@peertube/peertube-node-utils'
import { PeerTubeServer } from '@peertube/peertube-server-commands'
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 }

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

View file

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

View file

@ -1,7 +1,5 @@
import { remove } from 'fs-extra'
import { logger } from 'packages/peertube-runner/shared'
import { remove } from 'fs-extra/esm'
import { join } from 'path'
import { buildUUID } from '@shared/extra-utils'
import {
RunnerJobVODAudioMergeTranscodingPayload,
RunnerJobVODHLSTranscodingPayload,
@ -9,9 +7,11 @@ import {
VODAudioMergeTranscodingSuccess,
VODHLSTranscodingSuccess,
VODWebVideoTranscodingSuccess
} from '@shared/models'
import { ConfigManager } from '../../../shared/config-manager'
import { buildFFmpegVOD, downloadInputFile, ProcessOptions, scheduleTranscodingProgress } from './common'
} from '@peertube/peertube-models'
import { buildUUID } from '@peertube/peertube-node-utils'
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>) {
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 () {
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 { io, Socket } from 'socket.io-client'
import { pick, shuffle, wait } from '@shared/core-utils'
import { PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
import { PeerTubeServer as PeerTubeServerCommand } from '@shared/server-commands'
import { ConfigManager } from '../shared'
import { IPCServer } from '../shared/ipc'
import { logger } from '../shared/logger'
import { JobWithToken, processJob } from './process'
import { isJobSupported } from './shared'
import { pick, shuffle, wait } from '@peertube/peertube-core-utils'
import { PeerTubeProblemDocument, ServerErrorCode } from '@peertube/peertube-models'
import { PeerTubeServer as PeerTubeServerCommand } from '@peertube/peertube-server-commands'
import { ConfigManager } from '../shared/index.js'
import { IPCServer } from '../shared/ipc/index.js'
import { logger } from '../shared/logger.js'
import { JobWithToken, processJob } from './process/index.js'
import { isJobSupported } from './shared/index.js'
type PeerTubeServer = PeerTubeServerCommand & {
runnerToken: string

View file

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

View file

@ -7,7 +7,7 @@ import {
RunnerJobVODHLSTranscodingPayload,
RunnerJobVODWebVideoTranscodingPayload,
VideoStudioTaskPayload
} from '@shared/models'
} from '@peertube/peertube-models'
const supportedMatrix = {
'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 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')

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 requestHTTPS, RequestOptions } from 'https'
import { logger } from './logger'
import { logger } from './logger.js'
export function downloadFile (options: {
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 { ensureDir } from 'fs-extra'
import { ensureDir } from 'fs-extra/esm'
import { Client as NetIPC } from 'net-ipc'
import { ConfigManager } from '../config-manager'
import { IPCReponse, IPCReponseData, IPCRequest } from './shared'
import { ConfigManager } from '../config-manager.js'
import { IPCReponse, IPCReponseData, IPCRequest } from './shared/index.js'
export class IPCClient {
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 { pick } from '@shared/core-utils'
import { RunnerServer } from '../../server'
import { ConfigManager } from '../config-manager'
import { logger } from '../logger'
import { IPCReponse, IPCReponseData, IPCRequest } from './shared'
import { pick } from '@peertube/peertube-core-utils'
import { RunnerServer } from '../../server/index.js'
import { ConfigManager } from '../config-manager.js'
import { logger } from '../logger.js'
import { IPCReponse, IPCReponseData, IPCRequest } from './shared/index.js'
export class IPCServer {
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 pretty from 'pino-pretty'
const logger = pino(pretty({
const logger = pino(pretty.default({
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": [
"tsconfig.eslint.json"
],
"EXPERIMENTAL_useSourceOfProjectReferenceRedirect": true,
"createDefaultProgram": false
},
"extends": [

View file

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

View file

@ -14,7 +14,7 @@
},
"scripts": {
"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'",
"webpack": "webpack",
"eslint": "eslint",
@ -24,6 +24,9 @@
"ngx-extractor": "ngx-extractor",
"stylelint": "stylelint"
},
"workspaces": [
"../packages/*"
],
"typings": "*.d.ts",
"devDependencies": {
"@angular-devkit/build-angular": "^16.0.2",
@ -57,6 +60,8 @@
"@peertube/maildev": "^1.2.0",
"@peertube/p2p-media-loader-core": "^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/xliffmerge": "^2.0.3",
"@popperjs/core": "^2.11.5",
@ -86,7 +91,7 @@
"buffer": "^6.0.3",
"chart.js": "^4.3.0",
"chartjs-plugin-zoom": "~2.0.1",
"chromedriver": "^113.0.0",
"chromedriver": "^115.0.1",
"core-js": "^3.22.8",
"css-loader": "^6.2.0",
"debug": "^4.3.1",
@ -122,6 +127,7 @@
"stylelint": "^15.1.0",
"stylelint-config-sass-guidelines": "^10.0.0",
"ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"tslib": "^2.4.0",
"typescript": "~4.9.5",
"video.js": "^7.19.2",

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'
import { ServerService } from '@app/core'
import { CustomMarkupService } from '@app/shared/shared-custom-markup'
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 = {
serverStats: ServerStats

View file

@ -11,7 +11,7 @@ import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { InstanceService } from '@app/shared/shared-instance'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
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 = {
subject?: string

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import { Routes } from '@angular/router'
import { EditCustomConfigComponent } from '@app/+admin/config/edit-custom-config'
import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models'
import { UserRight } from '@peertube/peertube-models'
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 { FormGroup } from '@angular/forms'
import { MenuService, ThemeService } from '@app/core'
import { HTMLServerConfig } from '@shared/models'
import { HTMLServerConfig } from '@peertube/peertube-models'
import { ConfigService } from '../shared/config.service'
@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 { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
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'
type ComponentCustomConfig = CustomConfig & {

View file

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

View file

@ -2,7 +2,7 @@
import { SelectOptionsItem } from 'src/types/select-options-item.model'
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'
import { FormGroup } from '@angular/forms'
import { HTMLServerConfig } from '@shared/models'
import { HTMLServerConfig } from '@peertube/peertube-models'
import { ConfigService } from '../shared/config.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 { Injectable } from '@angular/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 { environment } from '../../../../environments/environment'

View file

@ -5,7 +5,7 @@ import { formatICU } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance'
import { DropdownAction } from '@app/shared/shared-main'
import { ActorFollow } from '@shared/models'
import { ActorFollow } from '@peertube/peertube-models'
@Component({
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 { AdvancedInputFilter } from '@app/shared/shared-forms'
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 { DropdownAction } from '@app/shared/shared-main'
import { formatICU } from '@app/helpers'

View file

@ -1,7 +1,7 @@
import { Routes } from '@angular/router'
import { VideoRedundanciesListComponent } from '@app/+admin/follows/video-redundancies-list'
import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models'
import { UserRight } from '@peertube/peertube-models'
import { FollowersListComponent } from './followers-list'
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 { ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
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 { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
import { VideosRedundancyStats } from '@shared/models/server'
@Component({
selector: 'my-video-redundancies-list',

View file

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core'
import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@shared/models'
import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@peertube/peertube-models'
@Component({
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 { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list'
import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models'
import { UserRight } from '@peertube/peertube-models'
import { RegistrationListComponent } from './registration-list'
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 { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ResultList, UserRegistration, UserRegistrationUpdateState } from '@shared/models'
import { arrayify } from '@peertube/peertube-core-utils'
import { ResultList, UserRegistration, UserRegistrationUpdateState } from '@peertube/peertube-models'
import { environment } from '../../../../environments/environment'
@Injectable()

View file

@ -3,7 +3,7 @@ import { Notifier, ServerService } from '@app/core'
import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
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 { 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 { AdvancedInputFilter } from '@app/shared/shared-forms'
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 { 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 { DropdownAction, VideoService } from '@app/shared/shared-main'
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 { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@Component({
selector: 'my-video-block-list',
@ -21,7 +21,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
blocklistTypeFilter: VideoBlacklistType = undefined
blocklistTypeFilter: VideoBlacklistType_Type
videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = []

View file

@ -6,7 +6,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction } from '@app/shared/shared-main'
import { BulkService } from '@app/shared/shared-moderation'
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'
@Component({

View file

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

View file

@ -14,7 +14,7 @@ import {
} from '@app/shared/form-validators/user-validators'
import { FormReactiveService } from '@app/shared/shared-forms'
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'
@Component({

View file

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

View file

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

View file

@ -11,7 +11,7 @@ import {
} from '@app/shared/form-validators/user-validators'
import { FormReactiveService } from '@app/shared/shared-forms'
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'
@Component({

View file

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

View file

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

View file

@ -5,8 +5,8 @@ import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main'
import { ResultList, VideoInclude, VideoPrivacy } from '@shared/models'
import { getAllPrivacies } from '@shared/core-utils'
import { ResultList, VideoInclude, VideoPrivacy } from '@peertube/peertube-models'
import { getAllPrivacies } from '@peertube/peertube-core-utils'
@Injectable()
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 { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
import { getAllFiles } from '@shared/core-utils'
import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
import { getAllFiles } from '@peertube/peertube-core-utils'
import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@peertube/peertube-models'
import { VideoAdminService } from './video-admin.service'
@Component({

View file

@ -1,6 +1,6 @@
import { Routes } from '@angular/router'
import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models'
import { UserRight } from '@peertube/peertube-models'
import { VideoListComponent } from './video-list.component'
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 { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core'
import { PluginService } from '@app/core/plugins/plugin.service'
import { compareSemVer } from '@shared/core-utils'
import { PeerTubePlugin, PluginType } from '@shared/models'
import { compareSemVer } from '@peertube/peertube-core-utils'
import { PeerTubePlugin, PluginType, PluginType_Type } from '@peertube/peertube-models'
@Component({
selector: 'my-plugin-list-installed',
@ -13,7 +13,7 @@ import { PeerTubePlugin, PluginType } from '@shared/models'
styleUrls: [ './plugin-list-installed.component.scss' ]
})
export class PluginListInstalledComponent implements OnInit {
pluginType: PluginType
pluginType: PluginType_Type
pagination: ComponentPagination = {
currentPage: 1,
@ -48,7 +48,7 @@ export class PluginListInstalledComponent implements OnInit {
this.route.queryParams.subscribe(query => {
if (!query['pluginType']) return
this.pluginType = parseInt(query['pluginType'], 10)
this.pluginType = parseInt(query['pluginType'], 10) as PluginType_Type
this.reloadPlugins()
})

View file

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

View file

@ -5,7 +5,7 @@ import { ActivatedRoute } from '@angular/router'
import { HooksService, Notifier, PluginService } from '@app/core'
import { BuildFormArgument } from '@app/shared/form-validators'
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'
@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 { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component'
import { UserRightGuard } from '@app/core'
import { UserRight } from '@shared/models'
import { UserRight } from '@peertube/peertube-models'
export const PluginsRoutes: Routes = [
{

View file

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

View file

@ -1,5 +1,5 @@
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'
@Component({
@ -11,7 +11,7 @@ import { PluginApiService } from './plugin-api.service'
export class PluginCardComponent {
@Input() plugin: PeerTubePluginIndex | PeerTubePlugin
@Input() version: string
@Input() pluginType: PluginType
@Input() pluginType: PluginType_Type
constructor (
private pluginApiService: PluginApiService

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