ref impl : yt-dlp wrapper
This commit is contained in:
parent
261736a139
commit
7cce1cc6c2
327
packages/media/ref/README.md
Normal file
327
packages/media/ref/README.md
Normal file
@ -0,0 +1,327 @@
|
||||
# yt-dlp-wrapper
|
||||
|
||||
A TypeScript wrapper library for [yt-dlp](https://github.com/yt-dlp/yt-dlp), a powerful command-line video downloader.
|
||||
|
||||
[](https://www.npmjs.com/package/yt-dlp-wrapper)
|
||||
[](https://github.com/yourusername/yt-dlp-wrapper/blob/main/LICENSE)
|
||||
|
||||
## Features
|
||||
|
||||
- 🔄 **Full TypeScript support** with comprehensive type definitions
|
||||
- 🧩 **Modular architecture** for easy integration into your projects
|
||||
- 🔍 **Zod validation** for reliable input/output handling
|
||||
- 📊 **Structured logging** with TSLog
|
||||
- 🛠️ **Command-line interface** built with yargs
|
||||
- ⚡ **Promise-based API** for easy async operations
|
||||
- 🔧 **Highly configurable** with sensible defaults
|
||||
- 🔄 **Progress tracking** for downloads
|
||||
- 💼 **Error handling** with clear error messages
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using this library, ensure you have:
|
||||
|
||||
1. **Node.js** (v14 or higher)
|
||||
2. **yt-dlp** installed on your system:
|
||||
- **Linux/macOS**: `brew install yt-dlp` or `pip install yt-dlp`
|
||||
- **Windows**: Download from [yt-dlp GitHub releases](https://github.com/yt-dlp/yt-dlp/releases)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Using npm
|
||||
npm install yt-dlp-wrapper
|
||||
|
||||
# Using yarn
|
||||
yarn add yt-dlp-wrapper
|
||||
|
||||
# Using pnpm
|
||||
pnpm add yt-dlp-wrapper
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
The library includes a command-line interface for common operations:
|
||||
|
||||
### Download a video
|
||||
|
||||
```bash
|
||||
# Basic download
|
||||
npx yt-dlp-wrapper download https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
# Download with specific format
|
||||
npx yt-dlp-wrapper download https://www.tiktok.com/@businessblurb/video/7479849082844892458 -f "best[height<=720]"
|
||||
|
||||
# Download to specific directory
|
||||
npx yt-dlp-wrapper download https://youtu.be/dQw4w9WgXcQ --output-dir "./downloads"
|
||||
```
|
||||
|
||||
### Get video information
|
||||
|
||||
```bash
|
||||
# Basic info
|
||||
npx yt-dlp-wrapper info https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
|
||||
# With JSON output
|
||||
npx yt-dlp-wrapper info https://www.tiktok.com/@businessblurb/video/7479849082844892458 --dump-json
|
||||
```
|
||||
|
||||
### List available formats
|
||||
|
||||
```bash
|
||||
npx yt-dlp-wrapper formats https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
```
|
||||
|
||||
### Help
|
||||
|
||||
```bash
|
||||
# General help
|
||||
npx yt-dlp-wrapper --help
|
||||
|
||||
# Command-specific help
|
||||
npx yt-dlp-wrapper download --help
|
||||
```
|
||||
|
||||
## API Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
// Create a new instance with default options
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
// Download a video
|
||||
async function downloadVideo() {
|
||||
try {
|
||||
const filePath = await ytdlp.downloadVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
||||
console.log(`Video downloaded to: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
downloadVideo();
|
||||
```
|
||||
|
||||
### Download with Options
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
async function downloadWithOptions() {
|
||||
try {
|
||||
const filePath = await ytdlp.downloadVideo('https://www.tiktok.com/@businessblurb/video/7479849082844892458', {
|
||||
format: 'bestvideo[height<=720]+bestaudio/best[height<=720]',
|
||||
outputDir: './downloads',
|
||||
filename: 'tiktok-video.mp4',
|
||||
subtitles: true,
|
||||
audioOnly: false,
|
||||
// Add any other options as needed
|
||||
});
|
||||
|
||||
console.log(`Video downloaded to: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
downloadWithOptions();
|
||||
```
|
||||
|
||||
### Get Video Information
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
async function getVideoInfo() {
|
||||
try {
|
||||
const info = await ytdlp.getVideoInfo('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
||||
console.log('Video title:', info.title);
|
||||
console.log('Duration:', info.duration);
|
||||
console.log('Uploader:', info.uploader);
|
||||
// Access other properties as needed
|
||||
} catch (error) {
|
||||
console.error('Failed to get video info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
getVideoInfo();
|
||||
```
|
||||
|
||||
### List Available Formats
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
async function listFormats() {
|
||||
try {
|
||||
const formats = await ytdlp.listFormats('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
||||
|
||||
// Display all formats
|
||||
formats.forEach(format => {
|
||||
console.log(`Format ID: ${format.formatId}`);
|
||||
console.log(`Resolution: ${format.resolution}`);
|
||||
console.log(`Extension: ${format.extension}`);
|
||||
console.log(`File size: ${format.filesize}`);
|
||||
console.log('---');
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to list formats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
listFormats();
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
// Create a new instance with custom options
|
||||
const ytdlp = new YtDlp({
|
||||
executablePath: '/usr/local/bin/yt-dlp', // Custom path to yt-dlp executable
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
verbose: true,
|
||||
// Add other global options as needed
|
||||
});
|
||||
|
||||
// Use as normal
|
||||
ytdlp.downloadVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
||||
.then(filePath => console.log(`Video downloaded to: ${filePath}`))
|
||||
.catch(error => console.error('Download failed:', error));
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Download Playlist
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
async function downloadPlaylist() {
|
||||
try {
|
||||
const result = await ytdlp.downloadVideo('https://www.youtube.com/playlist?list=PLexamplelistID', {
|
||||
outputDir: './playlists',
|
||||
playlistItems: '1-5', // Only download the first 5 videos
|
||||
limit: 5,
|
||||
format: 'best[height<=480]' // Lower quality to save space
|
||||
});
|
||||
|
||||
console.log(`Playlist downloaded to: ${result}`);
|
||||
} catch (error) {
|
||||
console.error('Playlist download failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
downloadPlaylist();
|
||||
```
|
||||
|
||||
### Progress Tracking
|
||||
|
||||
```typescript
|
||||
import { YtDlp } from 'yt-dlp-wrapper';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
async function downloadWithProgress() {
|
||||
try {
|
||||
const filePath = await ytdlp.downloadVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ', {
|
||||
onProgress: (progress) => {
|
||||
console.log(`Download progress: ${progress.percent}%`);
|
||||
console.log(`Speed: ${progress.speed}`);
|
||||
console.log(`ETA: ${progress.eta}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Video downloaded to: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
downloadWithProgress();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library provides detailed error information:
|
||||
|
||||
```typescript
|
||||
import { YtDlp, YtDlpError } from 'yt-dlp-wrapper';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
async function handleErrors() {
|
||||
try {
|
||||
await ytdlp.downloadVideo('https://invalid-url.com/video');
|
||||
} catch (error) {
|
||||
if (error instanceof YtDlpError) {
|
||||
console.error(`YtDlp Error: ${error.message}`);
|
||||
console.error(`Error Code: ${error.code}`);
|
||||
console.error(`Command: ${error.command}`);
|
||||
} else {
|
||||
console.error('Unknown error:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleErrors();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### YtDlp Class
|
||||
|
||||
#### Constructor
|
||||
|
||||
```typescript
|
||||
new YtDlp(options?: YtDlpOptions)
|
||||
```
|
||||
|
||||
**YtDlpOptions:**
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
|--------|------|-------------|---------|
|
||||
| executablePath | string | Path to yt-dlp executable | 'yt-dlp' |
|
||||
| userAgent | string | User agent to use for requests | Default browser UA |
|
||||
| verbose | boolean | Enable verbose output | false |
|
||||
| quiet | boolean | Suppress output | false |
|
||||
|
||||
#### Methods
|
||||
|
||||
**downloadVideo(url, options?)**
|
||||
Downloads a video from the given URL.
|
||||
|
||||
**getVideoInfo(url, options?)**
|
||||
Gets information about a video.
|
||||
|
||||
**listFormats(url, options?)**
|
||||
Lists available formats for a video.
|
||||
|
||||
**checkInstallation()**
|
||||
Verifies that yt-dlp is installed and working.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) - The amazing tool this library wraps
|
||||
- All the contributors to the open-source libraries used in this project
|
||||
|
||||
49
packages/media/ref/package.json
Normal file
49
packages/media/ref/package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "yt-dlp-wrapper",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript wrapper for yt-dlp with CLI interface",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"bin": {
|
||||
"yt-dlp-wrapper": "dist/cli.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"start": "node dist/cli.js",
|
||||
"dev": "ts-node --esm src/cli.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"yt-dlp",
|
||||
"video",
|
||||
"downloader",
|
||||
"cli"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/yargs": "^17.0.33",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslog": "^4.9.3",
|
||||
"typescript": "^5.8.2",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/coverage-v8": "^3.0.8",
|
||||
"@vitest/ui": "^3.0.8",
|
||||
"vitest": "^3.0.8"
|
||||
}
|
||||
}
|
||||
1641
packages/media/ref/pnpm-lock.yaml
Normal file
1641
packages/media/ref/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
183
packages/media/ref/src/cli.ts
Normal file
183
packages/media/ref/src/cli.ts
Normal file
@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env node
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import { YtDlp } from './ytdlp.js';
|
||||
import { logger } from './logger.js';
|
||||
import { FormatOptionsSchema, VideoInfoOptionsSchema, DownloadOptionsSchema } from './types.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ytdlp = new YtDlp();
|
||||
|
||||
const cli = yargs(hideBin(process.argv))
|
||||
.scriptName('ytdlp-ts')
|
||||
.usage('$0 <cmd> [args]')
|
||||
.command(
|
||||
'download [url]',
|
||||
'Download a video',
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.positional('url', {
|
||||
type: 'string',
|
||||
describe: 'URL of the video to download',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('format', {
|
||||
type: 'string',
|
||||
describe: 'Video format code',
|
||||
alias: 'f',
|
||||
})
|
||||
.option('output', {
|
||||
type: 'string',
|
||||
describe: 'Output filename template',
|
||||
alias: 'o',
|
||||
})
|
||||
.option('quiet', {
|
||||
type: 'boolean',
|
||||
describe: 'Activate quiet mode',
|
||||
alias: 'q',
|
||||
default: false,
|
||||
})
|
||||
.option('verbose', {
|
||||
type: 'boolean',
|
||||
describe: 'Print various debugging information',
|
||||
alias: 'v',
|
||||
default: false,
|
||||
});
|
||||
},
|
||||
async (argv) => {
|
||||
try {
|
||||
logger.info(`Downloading video from ${argv.url}`);
|
||||
|
||||
// Parse and validate options using Zod
|
||||
const options = DownloadOptionsSchema.parse({
|
||||
format: argv.format,
|
||||
output: argv.output,
|
||||
quiet: argv.quiet,
|
||||
verbose: argv.verbose,
|
||||
});
|
||||
|
||||
await ytdlp.downloadVideo(argv.url as string, options);
|
||||
logger.info('Download completed successfully');
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.error('Invalid options:', error.errors);
|
||||
} else {
|
||||
logger.error('Failed to download video:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'info [url]',
|
||||
'Get video information',
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.positional('url', {
|
||||
type: 'string',
|
||||
describe: 'URL of the video',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('dump-json', {
|
||||
type: 'boolean',
|
||||
describe: 'Output JSON information',
|
||||
default: false,
|
||||
})
|
||||
.option('flat-playlist', {
|
||||
type: 'boolean',
|
||||
describe: 'Flat playlist output',
|
||||
default: false,
|
||||
});
|
||||
},
|
||||
async (argv) => {
|
||||
try {
|
||||
logger.info(`Getting info for video: ${argv.url}`);
|
||||
|
||||
// Parse and validate options using Zod
|
||||
const options = VideoInfoOptionsSchema.parse({
|
||||
dumpJson: argv.dumpJson,
|
||||
flatPlaylist: argv.flatPlaylist,
|
||||
});
|
||||
|
||||
const info = await ytdlp.getVideoInfo(argv.url as string, options);
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.error('Invalid options:', error.errors);
|
||||
} else {
|
||||
logger.error('Failed to get video info:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
)
|
||||
.command(
|
||||
'formats [url]',
|
||||
'List available formats of a video',
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.positional('url', {
|
||||
type: 'string',
|
||||
describe: 'URL of the video',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('all', {
|
||||
type: 'boolean',
|
||||
describe: 'Show all available formats',
|
||||
default: false,
|
||||
});
|
||||
},
|
||||
async (argv) => {
|
||||
try {
|
||||
logger.info(`Getting available formats for video: ${argv.url}`);
|
||||
|
||||
// Parse and validate options using Zod
|
||||
const options = FormatOptionsSchema.parse({
|
||||
all: argv.all,
|
||||
});
|
||||
|
||||
const formats = await ytdlp.listFormats(argv.url as string, options);
|
||||
console.log(formats);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
logger.error('Invalid options:', error.errors);
|
||||
} else {
|
||||
logger.error('Failed to list formats:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
)
|
||||
.example(
|
||||
'$0 download https://www.tiktok.com/@businessblurb/video/7479849082844892458',
|
||||
'Download a TikTok video'
|
||||
)
|
||||
.example(
|
||||
'$0 download https://youtu.be/dQw4w9WgXcQ -f "bestvideo[height<=720]+bestaudio/best[height<=720]"',
|
||||
'Download YouTube video in 720p or lower quality'
|
||||
)
|
||||
.example(
|
||||
'$0 info https://www.tiktok.com/@businessblurb/video/7479849082844892458 --dump-json',
|
||||
'Get TikTok video info as JSON'
|
||||
)
|
||||
.example(
|
||||
'$0 formats https://youtu.be/dQw4w9WgXcQ',
|
||||
'List available formats for a YouTube video'
|
||||
)
|
||||
.demandCommand(1, 'You need to specify a command')
|
||||
.strict()
|
||||
.help()
|
||||
.alias('h', 'help')
|
||||
.version()
|
||||
.alias('V', 'version')
|
||||
.wrap(100); // Fixed width value instead of yargs.terminalWidth() which isn't compatible with ESM
|
||||
|
||||
// If this file is run directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
// Execute the CLI
|
||||
cli.parse();
|
||||
}
|
||||
|
||||
// Export for use as a library
|
||||
export default cli;
|
||||
|
||||
28
packages/media/ref/src/index.ts
Normal file
28
packages/media/ref/src/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// Export YtDlp class
|
||||
export { YtDlp } from './ytdlp.js';
|
||||
|
||||
// Export all types and schemas
|
||||
export {
|
||||
// Core types
|
||||
YtDlpOptions,
|
||||
DownloadOptions,
|
||||
FormatOptions,
|
||||
VideoInfoOptions,
|
||||
VideoFormat,
|
||||
VideoInfo,
|
||||
|
||||
// Zod schemas
|
||||
YtDlpOptionsSchema,
|
||||
DownloadOptionsSchema,
|
||||
FormatOptionsSchema,
|
||||
VideoInfoOptionsSchema,
|
||||
VideoFormatSchema,
|
||||
VideoInfoSchema,
|
||||
} from './types.js';
|
||||
|
||||
// Export logger
|
||||
export { logger } from './logger.js';
|
||||
|
||||
// Re-export CLI for direct usage
|
||||
export { default as cli } from './cli.js';
|
||||
|
||||
55
packages/media/ref/src/logger.ts
Normal file
55
packages/media/ref/src/logger.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Logger, ILogObj } from 'tslog';
|
||||
|
||||
// Configure log levels
|
||||
export enum LogLevel {
|
||||
SILLY = 'silly',
|
||||
TRACE = 'trace',
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal'
|
||||
}
|
||||
|
||||
// Mapping from string LogLevel to numeric values expected by tslog
|
||||
const logLevelToTsLogLevel: Record<LogLevel, number> = {
|
||||
[LogLevel.SILLY]: 0,
|
||||
[LogLevel.TRACE]: 1,
|
||||
[LogLevel.DEBUG]: 2,
|
||||
[LogLevel.INFO]: 3,
|
||||
[LogLevel.WARN]: 4,
|
||||
[LogLevel.ERROR]: 5,
|
||||
[LogLevel.FATAL]: 6
|
||||
};
|
||||
|
||||
// Convert a LogLevel string to its corresponding numeric value
|
||||
const getNumericLogLevel = (level: LogLevel): number => {
|
||||
return logLevelToTsLogLevel[level];
|
||||
};
|
||||
// Custom transport for logs if needed
|
||||
const logToTransport = (logObject: ILogObj) => {
|
||||
// Here you can implement custom transport like file or external service
|
||||
// For example, log to file or send to a log management service
|
||||
// console.log("Custom transport:", JSON.stringify(logObject));
|
||||
};
|
||||
|
||||
// Create the logger instance
|
||||
export const logger = new Logger({
|
||||
name: "yt-dlp-wrapper"
|
||||
});
|
||||
|
||||
// Add transport if needed
|
||||
// logger.attachTransport(
|
||||
// {
|
||||
// silly: logToTransport,
|
||||
// debug: logToTransport,
|
||||
// trace: logToTransport,
|
||||
// info: logToTransport,
|
||||
// warn: logToTransport,
|
||||
// error: logToTransport,
|
||||
// fatal: logToTransport,
|
||||
// },
|
||||
// LogLevel.INFO
|
||||
// );
|
||||
|
||||
export default logger;
|
||||
224
packages/media/ref/src/types.ts
Normal file
224
packages/media/ref/src/types.ts
Normal file
@ -0,0 +1,224 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// Basic YouTube DLP options schema
|
||||
export const YtDlpOptionsSchema = z.object({
|
||||
// Path to the yt-dlp executable
|
||||
executablePath: z.string().optional(),
|
||||
|
||||
// Output options
|
||||
output: z.string().optional(),
|
||||
format: z.string().optional(),
|
||||
formatSort: z.string().optional(),
|
||||
mergeOutputFormat: z.enum(['mp4', 'flv', 'webm', 'mkv', 'avi']).optional(),
|
||||
|
||||
// Download options
|
||||
limit: z.number().int().positive().optional(),
|
||||
maxFilesize: z.string().optional(),
|
||||
minFilesize: z.string().optional(),
|
||||
|
||||
// Filesystem options
|
||||
noOverwrites: z.boolean().optional(),
|
||||
continue: z.boolean().optional(),
|
||||
noPart: z.boolean().optional(),
|
||||
|
||||
// Thumbnail options
|
||||
writeThumbnail: z.boolean().optional(),
|
||||
writeAllThumbnails: z.boolean().optional(),
|
||||
|
||||
// Subtitles options
|
||||
writeSubtitles: z.boolean().optional(),
|
||||
writeAutoSubtitles: z.boolean().optional(),
|
||||
subLang: z.string().optional(),
|
||||
|
||||
// Authentication options
|
||||
username: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
|
||||
// Video selection options
|
||||
playlistStart: z.number().int().positive().optional(),
|
||||
playlistEnd: z.number().int().positive().optional(),
|
||||
playlistItems: z.string().optional(),
|
||||
|
||||
// Post-processing options
|
||||
extractAudio: z.boolean().optional(),
|
||||
audioFormat: z.enum(['best', 'aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']).optional(),
|
||||
audioQuality: z.string().optional(),
|
||||
remuxVideo: z.enum(['mp4', 'mkv', 'flv', 'webm', 'mov', 'avi']).optional(),
|
||||
recodeVideo: z.enum(['mp4', 'flv', 'webm', 'mkv', 'avi']).optional(),
|
||||
|
||||
// Verbosity and simulation options
|
||||
quiet: z.boolean().optional(),
|
||||
verbose: z.boolean().optional(),
|
||||
noWarnings: z.boolean().optional(),
|
||||
simulate: z.boolean().optional(),
|
||||
|
||||
// Workarounds
|
||||
noCheckCertificates: z.boolean().optional(),
|
||||
preferInsecure: z.boolean().optional(),
|
||||
userAgent: z.string().optional(),
|
||||
|
||||
// Extra arguments as string array
|
||||
extraArgs: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
// Type derived from the schema
|
||||
export type YtDlpOptions = z.infer<typeof YtDlpOptionsSchema>;
|
||||
|
||||
// Video information schema
|
||||
export const VideoInfoSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
formats: z.array(
|
||||
z.object({
|
||||
format_id: z.string(),
|
||||
format: z.string(),
|
||||
ext: z.string(),
|
||||
resolution: z.string().optional(),
|
||||
fps: z.number().optional(),
|
||||
filesize: z.number().optional(),
|
||||
tbr: z.number().optional(),
|
||||
protocol: z.string(),
|
||||
vcodec: z.string(),
|
||||
acodec: z.string(),
|
||||
})
|
||||
),
|
||||
thumbnails: z.array(
|
||||
z.object({
|
||||
url: z.string(),
|
||||
height: z.number().optional(),
|
||||
width: z.number().optional(),
|
||||
})
|
||||
).optional(),
|
||||
description: z.string().optional(),
|
||||
upload_date: z.string().optional(),
|
||||
uploader: z.string().optional(),
|
||||
uploader_id: z.string().optional(),
|
||||
uploader_url: z.string().optional(),
|
||||
channel_id: z.string().optional(),
|
||||
channel_url: z.string().optional(),
|
||||
duration: z.number().optional(),
|
||||
view_count: z.number().optional(),
|
||||
like_count: z.number().optional(),
|
||||
dislike_count: z.number().optional(),
|
||||
average_rating: z.number().optional(),
|
||||
age_limit: z.number().optional(),
|
||||
webpage_url: z.string(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
is_live: z.boolean().optional(),
|
||||
was_live: z.boolean().optional(),
|
||||
playable_in_embed: z.boolean().optional(),
|
||||
availability: z.string().optional(),
|
||||
});
|
||||
|
||||
export type VideoInfo = z.infer<typeof VideoInfoSchema>;
|
||||
|
||||
// Download result schema
|
||||
export const DownloadResultSchema = z.object({
|
||||
videoInfo: VideoInfoSchema,
|
||||
filePath: z.string(),
|
||||
downloadedBytes: z.number().optional(),
|
||||
elapsedTime: z.number().optional(),
|
||||
averageSpeed: z.number().optional(), // in bytes/s
|
||||
success: z.boolean(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
export type DownloadResult = z.infer<typeof DownloadResultSchema>;
|
||||
|
||||
// Command execution result schema
|
||||
export const CommandResultSchema = z.object({
|
||||
command: z.string(),
|
||||
stdout: z.string(),
|
||||
stderr: z.string(),
|
||||
success: z.boolean(),
|
||||
exitCode: z.number(),
|
||||
});
|
||||
|
||||
export type CommandResult = z.infer<typeof CommandResultSchema>;
|
||||
|
||||
// Progress update schema for download progress events
|
||||
export const ProgressUpdateSchema = z.object({
|
||||
videoId: z.string(),
|
||||
percent: z.number().min(0).max(100),
|
||||
totalSize: z.number().optional(),
|
||||
downloadedBytes: z.number(),
|
||||
speed: z.number(), // in bytes/s
|
||||
eta: z.number().optional(), // in seconds
|
||||
status: z.enum(['downloading', 'finished', 'error']),
|
||||
message: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ProgressUpdate = z.infer<typeof ProgressUpdateSchema>;
|
||||
|
||||
// Error types
|
||||
export enum YtDlpErrorType {
|
||||
PROCESS_ERROR = 'PROCESS_ERROR',
|
||||
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||
DOWNLOAD_ERROR = 'DOWNLOAD_ERROR',
|
||||
UNSUPPORTED_URL = 'UNSUPPORTED_URL',
|
||||
NETWORK_ERROR = 'NETWORK_ERROR',
|
||||
}
|
||||
|
||||
// Custom error schema
|
||||
export const YtDlpErrorSchema = z.object({
|
||||
type: z.nativeEnum(YtDlpErrorType),
|
||||
message: z.string(),
|
||||
details: z.record(z.any()).optional(),
|
||||
command: z.string().optional(),
|
||||
});
|
||||
export type YtDlpError = z.infer<typeof YtDlpErrorSchema>;
|
||||
|
||||
// Download options schema
|
||||
export const DownloadOptionsSchema = z.object({
|
||||
outputDir: z.string().optional(),
|
||||
format: z.string().optional(),
|
||||
outputTemplate: z.string().optional(),
|
||||
audioOnly: z.boolean().optional(),
|
||||
audioFormat: z.string().optional(),
|
||||
subtitles: z.union([z.boolean(), z.array(z.string())]).optional(),
|
||||
maxFileSize: z.number().optional(),
|
||||
rateLimit: z.string().optional(),
|
||||
additionalArgs: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export type DownloadOptions = z.infer<typeof DownloadOptionsSchema>;
|
||||
|
||||
// Format options schema for listing video formats
|
||||
export const FormatOptionsSchema = z.object({
|
||||
all: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
export type FormatOptions = z.infer<typeof FormatOptionsSchema>;
|
||||
|
||||
// Video info options schema
|
||||
export const VideoInfoOptionsSchema = z.object({
|
||||
dumpJson: z.boolean().optional().default(false),
|
||||
flatPlaylist: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
export type VideoInfoOptions = z.infer<typeof VideoInfoOptionsSchema>;
|
||||
|
||||
// Video format schema representing a single format option returned by yt-dlp
|
||||
export const VideoFormatSchema = z.object({
|
||||
format_id: z.string(),
|
||||
format: z.string(),
|
||||
ext: z.string(),
|
||||
resolution: z.string().optional(),
|
||||
fps: z.number().optional(),
|
||||
filesize: z.number().optional(),
|
||||
tbr: z.number().optional(),
|
||||
protocol: z.string(),
|
||||
vcodec: z.string(),
|
||||
acodec: z.string(),
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
url: z.string().optional(),
|
||||
format_note: z.string().optional(),
|
||||
container: z.string().optional(),
|
||||
quality: z.number().optional(),
|
||||
preference: z.number().optional(),
|
||||
});
|
||||
|
||||
export type VideoFormat = z.infer<typeof VideoFormatSchema>;
|
||||
|
||||
245
packages/media/ref/src/ytdlp.ts
Normal file
245
packages/media/ref/src/ytdlp.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import { exec, spawn } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { VideoInfo, DownloadOptions, YtDlpOptions, FormatOptions, VideoInfoOptions } from './types.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* A wrapper class for the yt-dlp command line tool
|
||||
*/
|
||||
export class YtDlp {
|
||||
private executable: string = 'yt-dlp';
|
||||
|
||||
/**
|
||||
* Create a new YtDlp instance
|
||||
* @param options Configuration options for yt-dlp
|
||||
*/
|
||||
constructor(private options: YtDlpOptions = {}) {
|
||||
if (options.executablePath) {
|
||||
this.executable = options.executablePath;
|
||||
}
|
||||
|
||||
logger.debug('YtDlp initialized with options:', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if yt-dlp is installed and accessible
|
||||
* @returns Promise resolving to true if yt-dlp is installed, false otherwise
|
||||
*/
|
||||
async isInstalled(): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`${this.executable} --version`);
|
||||
logger.debug(`yt-dlp version: ${stdout.trim()}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.warn('yt-dlp is not installed or not found in PATH');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a video from a given URL
|
||||
* @param url The URL of the video to download
|
||||
* @param options Download options
|
||||
* @returns Promise resolving to the path of the downloaded file
|
||||
*/
|
||||
async downloadVideo(url: string, options: DownloadOptions = {}): Promise<string> {
|
||||
if (!url) {
|
||||
throw new Error('URL is required');
|
||||
}
|
||||
|
||||
logger.info(`Downloading video from: ${url}`);
|
||||
|
||||
// Prepare output directory
|
||||
const outputDir = options.outputDir || '.';
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Build command arguments
|
||||
const args: string[] = [];
|
||||
|
||||
// Format selection
|
||||
if (options.format) {
|
||||
args.push('-f', options.format);
|
||||
}
|
||||
|
||||
// Output template
|
||||
const outputTemplate = options.outputTemplate || '%(title)s.%(ext)s';
|
||||
args.push('-o', path.join(outputDir, outputTemplate));
|
||||
|
||||
// Add other options
|
||||
if (options.audioOnly) {
|
||||
args.push('-x');
|
||||
if (options.audioFormat) {
|
||||
args.push('--audio-format', options.audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.subtitles) {
|
||||
args.push('--write-subs');
|
||||
if (Array.isArray(options.subtitles)) {
|
||||
args.push('--sub-lang', options.subtitles.join(','));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.maxFileSize) {
|
||||
args.push('--max-filesize', options.maxFileSize.toString());
|
||||
}
|
||||
|
||||
if (options.rateLimit) {
|
||||
args.push('--limit-rate', options.rateLimit);
|
||||
}
|
||||
|
||||
// Add custom arguments if provided
|
||||
if (options.additionalArgs) {
|
||||
args.push(...options.additionalArgs);
|
||||
}
|
||||
|
||||
// Add the URL
|
||||
args.push(url);
|
||||
|
||||
logger.debug('Executing command:', `${this.executable} ${args.join(' ')}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const ytdlpProcess = spawn(this.executable, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let downloadedFile: string | null = null;
|
||||
|
||||
ytdlpProcess.stdout.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
stdout += output;
|
||||
logger.debug(output.trim());
|
||||
|
||||
// Try to extract the output filename
|
||||
const destinationMatch = output.match(/Destination: (.+)/);
|
||||
if (destinationMatch && destinationMatch[1]) {
|
||||
downloadedFile = destinationMatch[1].trim();
|
||||
}
|
||||
|
||||
// Alternative method to extract the output filename
|
||||
const alreadyDownloadedMatch = output.match(/\[download\] (.+) has already been downloaded/);
|
||||
if (alreadyDownloadedMatch && alreadyDownloadedMatch[1]) {
|
||||
downloadedFile = alreadyDownloadedMatch[1].trim();
|
||||
}
|
||||
});
|
||||
|
||||
ytdlpProcess.stderr.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
stderr += output;
|
||||
logger.error(output.trim());
|
||||
});
|
||||
|
||||
ytdlpProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
if (downloadedFile) {
|
||||
logger.info(`Successfully downloaded: ${downloadedFile}`);
|
||||
resolve(downloadedFile);
|
||||
} else {
|
||||
// Try to find the downloaded file from stdout if it wasn't captured
|
||||
const fileMatch = stdout.match(/\[download\] (.+?) has already been downloaded/);
|
||||
if (fileMatch && fileMatch[1]) {
|
||||
logger.info(`Successfully downloaded: ${fileMatch[1]}`);
|
||||
resolve(fileMatch[1]);
|
||||
} else {
|
||||
logger.info('Download successful, but could not determine the output file name');
|
||||
resolve('Download completed');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`yt-dlp exited with code ${code}: ${stderr}`));
|
||||
}
|
||||
});
|
||||
|
||||
ytdlpProcess.on('error', (err) => {
|
||||
logger.error('Failed to start yt-dlp process:', err);
|
||||
reject(new Error(`Failed to start yt-dlp: ${err.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a video without downloading it
|
||||
* @param url The URL of the video to get information for
|
||||
* @returns Promise resolving to video information
|
||||
*/
|
||||
async getVideoInfo(url: string, options: VideoInfoOptions = { dumpJson: false, flatPlaylist: false }): Promise<VideoInfo> {
|
||||
if (!url) {
|
||||
throw new Error('URL is required');
|
||||
}
|
||||
|
||||
logger.info(`Getting video info for: ${url}`);
|
||||
|
||||
try {
|
||||
// Build command with options
|
||||
const args: string[] = ['--dump-json'];
|
||||
|
||||
// Add user agent if specified in global options
|
||||
if (this.options.userAgent) {
|
||||
args.push('--user-agent', this.options.userAgent);
|
||||
}
|
||||
|
||||
// Add VideoInfoOptions flags
|
||||
if (options.flatPlaylist) {
|
||||
args.push('--flat-playlist');
|
||||
}
|
||||
|
||||
args.push(url);
|
||||
|
||||
const { stdout } = await execAsync(`${this.executable} ${args.join(' ')}`);
|
||||
|
||||
const videoInfo = JSON.parse(stdout);
|
||||
logger.debug('Video info retrieved successfully');
|
||||
|
||||
return videoInfo;
|
||||
} catch (error) {
|
||||
logger.error('Failed to get video info:', error);
|
||||
throw new Error(`Failed to get video info: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List available formats for a video
|
||||
* @param url The URL of the video to get formats for
|
||||
* @returns Promise resolving to a string containing format information
|
||||
*/
|
||||
async listFormats(url: string, options: FormatOptions = { all: false }): Promise<string> {
|
||||
if (!url) {
|
||||
throw new Error('URL is required');
|
||||
}
|
||||
|
||||
logger.info(`Listing formats for: ${url}`);
|
||||
|
||||
try {
|
||||
// Build command with options
|
||||
const formatFlag = options.all ? '--list-formats-all' : '-F';
|
||||
|
||||
const { stdout } = await execAsync(`${this.executable} ${formatFlag} ${url}`);
|
||||
logger.debug('Format list retrieved successfully');
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
logger.error('Failed to list formats:', error);
|
||||
throw new Error(`Failed to list formats: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the yt-dlp executable
|
||||
* @param path Path to the yt-dlp executable
|
||||
*/
|
||||
setExecutablePath(path: string): void {
|
||||
if (!path) {
|
||||
throw new Error('Executable path cannot be empty');
|
||||
}
|
||||
this.executable = path;
|
||||
logger.debug(`yt-dlp executable path set to: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default YtDlp;
|
||||
|
||||
118
packages/media/ref/tsconfig.json
Normal file
118
packages/media/ref/tsconfig.json
Normal file
@ -0,0 +1,118 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"lib": ["es2020", "dom"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "libReplacement": true, /* Enable lib replacement. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "NodeNext", /* Specify what module code is generated. */
|
||||
"rootDir": "./src", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
"moduleDetection": "force", /* Control what method is used to detect module-format JS files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
|
||||
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user