veodl 1.12.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +183 -100
- package/bin/veo.js +47 -2
- package/docs/AGENT_GUIDE.md +1 -0
- package/native/android-arm64/veo +0 -0
- package/native/darwin-arm64/veo +0 -0
- package/native/darwin-x64/veo +0 -0
- package/native/linux-arm64/veo +0 -0
- package/native/linux-x64/veo +0 -0
- package/native/win32-arm64/veo.exe +0 -0
- package/native/win32-x64/veo.exe +0 -0
- package/package.json +53 -61
- package/src/alias.js +0 -258
- package/src/backend-update.js +0 -191
- package/src/backend.js +0 -548
- package/src/changes.js +0 -278
- package/src/cli.js +0 -781
- package/src/compatibility.js +0 -39
- package/src/config-clipboard.js +0 -67
- package/src/config-diagnostics.js +0 -155
- package/src/config-editor.js +0 -404
- package/src/config-errors.js +0 -64
- package/src/config-reset.js +0 -39
- package/src/config-template.js +0 -227
- package/src/config.js +0 -307
- package/src/disk-space.js +0 -68
- package/src/doctor.js +0 -302
- package/src/download-cache.js +0 -31
- package/src/downloader.js +0 -669
- package/src/execution.js +0 -68
- package/src/experimental-fragments.js +0 -29
- package/src/flush.js +0 -66
- package/src/history.js +0 -173
- package/src/inline-region.js +0 -72
- package/src/inspect-media.js +0 -179
- package/src/interactive.js +0 -120
- package/src/jobs.js +0 -218
- package/src/legacy-config-comments.js +0 -106
- package/src/naming.js +0 -54
- package/src/open-file.js +0 -15
- package/src/option-suggestions.js +0 -43
- package/src/output.js +0 -81
- package/src/path-links.js +0 -53
- package/src/paths.js +0 -26
- package/src/playlist.js +0 -31
- package/src/progress.js +0 -341
- package/src/run-archive.js +0 -148
- package/src/runs.js +0 -346
- package/src/source-discovery.js +0 -303
- package/src/state.js +0 -27
- package/src/stats.js +0 -63
- package/src/terminal-size.js +0 -8
- package/src/terminal-title.js +0 -19
- package/src/tool-setup.js +0 -151
- package/src/uninstall.js +0 -222
- package/src/updater.js +0 -229
- package/src/utils.js +0 -208
- package/src/version.js +0 -38
package/src/alias.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { chmod, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { commandOutput } from './output.js';
|
|
4
|
-
import { findOnPath } from './backend.js';
|
|
5
|
-
import { cleanText, readableError } from './utils.js';
|
|
6
|
-
|
|
7
|
-
export const BUILTIN_ALIASES = ['veo', 'veodl'];
|
|
8
|
-
export const ALIAS_MARKER = 'veo-alias';
|
|
9
|
-
|
|
10
|
-
export const ALIAS_HELP = `veo alias - manage extra command names for veo
|
|
11
|
-
|
|
12
|
-
Usage:
|
|
13
|
-
veo alias List command names (same as: veo alias list)
|
|
14
|
-
veo alias list [--json] Show veo, veodl plus custom wrappers
|
|
15
|
-
veo alias add <name> [--force] [--json]
|
|
16
|
-
Create <name> as a wrapper that calls veo
|
|
17
|
-
veo alias remove <name> [--json] Delete a wrapper (also: veo alias rm)
|
|
18
|
-
veo alias --help Show this help
|
|
19
|
-
|
|
20
|
-
Names use 2-31 lowercase letters, digits or hyphens and start with a letter,
|
|
21
|
-
e.g. veo-dl. The wrapper is created next to the veo command found on PATH and
|
|
22
|
-
simply forwards to veo, so it survives veo updates. Removing veodl only
|
|
23
|
-
removes that shortcut; veo update recreates the shipped names. veo itself can
|
|
24
|
-
never be removed. Use --bin-dir <path> to override the target directory.
|
|
25
|
-
`;
|
|
26
|
-
|
|
27
|
-
export function validateAliasName(input) {
|
|
28
|
-
const raw = String(input ?? '').trim();
|
|
29
|
-
if (!raw) throw new Error('An alias name is required. Use: veo alias add <name>');
|
|
30
|
-
if (cleanText(raw) !== raw) throw new Error(`Invalid alias name: ${raw}. Use plain text without escapes.`);
|
|
31
|
-
if (!/^[a-z][a-z0-9-]{1,30}$/.test(raw)) {
|
|
32
|
-
throw new Error(`Invalid alias name "${raw}". Use 2-31 lowercase letters, digits or hyphens, starting with a letter (e.g. veo-dl).`);
|
|
33
|
-
}
|
|
34
|
-
return raw;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function aliasFiles(binDir, name, platform = process.platform) {
|
|
38
|
-
if (platform === 'win32') {
|
|
39
|
-
return [path.join(binDir, name), path.join(binDir, `${name}.cmd`), path.join(binDir, `${name}.ps1`)];
|
|
40
|
-
}
|
|
41
|
-
return [path.join(binDir, name)];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function posixWrapper(name) {
|
|
45
|
-
return `#!/bin/sh\n# ${ALIAS_MARKER}: ${name}\nexec veo "$@"\n`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function windowsCmdWrapper(name) {
|
|
49
|
-
return `@echo off\r\nREM ${ALIAS_MARKER}: ${name}\r\nveo %*\r\n`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function windowsShWrapper(name) {
|
|
53
|
-
return `#!/bin/sh\n# ${ALIAS_MARKER}: ${name}\nexec veo "$@"\n`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function windowsPsWrapper(name) {
|
|
57
|
-
return `# ${ALIAS_MARKER}: ${name}\nveo @args\n`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function exists(file, { statImpl = stat } = {}) {
|
|
61
|
-
try {
|
|
62
|
-
const info = await statImpl(file);
|
|
63
|
-
return info.isFile();
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (error?.code === 'ENOENT') return false;
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function isManaged(file, { readFileImpl = readFile } = {}) {
|
|
71
|
-
try {
|
|
72
|
-
const text = await readFileImpl(file, 'utf8');
|
|
73
|
-
return text.slice(0, 4096).includes(ALIAS_MARKER);
|
|
74
|
-
} catch {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function resolveBinDir({ binDir, platform = process.platform, env = process.env, find = findOnPath } = {}) {
|
|
80
|
-
if (binDir) return path.resolve(binDir);
|
|
81
|
-
const found = await find(['veo'], { platform, env });
|
|
82
|
-
if (!found) {
|
|
83
|
-
throw new Error('Could not locate the veo command on PATH. Install veo globally (npm install -g veodl) or pass --bin-dir <path>.');
|
|
84
|
-
}
|
|
85
|
-
return path.dirname(found);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function aliasStatus(name, { binDir, platform = process.platform, ...fs } = {}) {
|
|
89
|
-
const files = aliasFiles(binDir, name, platform);
|
|
90
|
-
const present = [];
|
|
91
|
-
let managed = false;
|
|
92
|
-
for (const file of files) {
|
|
93
|
-
if (await exists(file, fs)) {
|
|
94
|
-
present.push(file);
|
|
95
|
-
if (await isManaged(file, fs)) managed = true;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return { name, builtin: BUILTIN_ALIASES.includes(name), present: present.length > 0, managed, paths: present };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function baseNameOf(file, platform) {
|
|
102
|
-
const base = path.basename(file);
|
|
103
|
-
if (platform === 'win32') {
|
|
104
|
-
if (base.toLowerCase().endsWith('.cmd')) return base.slice(0, -4);
|
|
105
|
-
if (base.toLowerCase().endsWith('.ps1')) return base.slice(0, -4);
|
|
106
|
-
}
|
|
107
|
-
return base;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function listAliases({ binDir, platform = process.platform, builtins = BUILTIN_ALIASES, readdirImpl = readdir, readFileImpl = readFile, statImpl = stat } = {}) {
|
|
111
|
-
const resolved = await resolveBinDir({ binDir, platform });
|
|
112
|
-
const entries = [];
|
|
113
|
-
for (const name of builtins) {
|
|
114
|
-
entries.push(await aliasStatus(name, { binDir: resolved, platform, readFileImpl, statImpl }));
|
|
115
|
-
}
|
|
116
|
-
const known = new Set(entries.map(entry => entry.name));
|
|
117
|
-
let files = [];
|
|
118
|
-
try {
|
|
119
|
-
files = await readdirImpl(resolved, { withFileTypes: true });
|
|
120
|
-
} catch (error) {
|
|
121
|
-
throw new Error(`Cannot list the command directory ${resolved}: ${readableError(error)}`);
|
|
122
|
-
}
|
|
123
|
-
for (const entry of files) {
|
|
124
|
-
if (!entry.isFile()) continue;
|
|
125
|
-
const full = path.join(resolved, entry.name);
|
|
126
|
-
let text = '';
|
|
127
|
-
try {
|
|
128
|
-
text = String(await readFileImpl(full, 'utf8')).slice(0, 4096);
|
|
129
|
-
} catch {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (!text.includes(ALIAS_MARKER)) continue;
|
|
133
|
-
const base = baseNameOf(entry.name, platform);
|
|
134
|
-
try {
|
|
135
|
-
validateAliasName(base);
|
|
136
|
-
} catch {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
if (known.has(base)) {
|
|
140
|
-
const existing = entries.find(item => item.name === base);
|
|
141
|
-
if (existing) existing.managed = true;
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
known.add(base);
|
|
145
|
-
entries.push({ name: base, builtin: false, present: true, managed: true, paths: [full] });
|
|
146
|
-
}
|
|
147
|
-
return { binDir: resolved, aliases: entries.sort((a, b) => a.name.localeCompare(b.name)) };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function addAlias(name, { binDir, platform = process.platform, force = false, writeFileImpl = writeFile, statImpl = stat, chmodImpl = chmod, mkdirImpl = mkdir } = {}) {
|
|
151
|
-
const valid = validateAliasName(name);
|
|
152
|
-
if (valid === 'veo') throw new Error('veo is already the main command.');
|
|
153
|
-
const resolved = await resolveBinDir({ binDir, platform });
|
|
154
|
-
await mkdirImpl(resolved, { recursive: true });
|
|
155
|
-
const files = aliasFiles(resolved, valid, platform);
|
|
156
|
-
const present = [];
|
|
157
|
-
for (const file of files) {
|
|
158
|
-
if (await exists(file, { statImpl })) present.push(file);
|
|
159
|
-
}
|
|
160
|
-
if (present.length && !force) {
|
|
161
|
-
throw new Error(`${valid} already exists (${present.map(file => path.basename(file)).join(', ')}). Use --force to replace it.`);
|
|
162
|
-
}
|
|
163
|
-
for (const file of present) await rm(file, { force: true });
|
|
164
|
-
if (platform === 'win32') {
|
|
165
|
-
await writeFileImpl(files[0], windowsShWrapper(valid), { mode: 0o755 });
|
|
166
|
-
await writeFileImpl(files[1], windowsCmdWrapper(valid));
|
|
167
|
-
await writeFileImpl(files[2], windowsPsWrapper(valid));
|
|
168
|
-
} else {
|
|
169
|
-
await writeFileImpl(files[0], posixWrapper(valid), { mode: 0o755 });
|
|
170
|
-
try {
|
|
171
|
-
await chmodImpl(files[0], 0o755);
|
|
172
|
-
} catch {
|
|
173
|
-
// Non-POSIX filesystems may ignore modes; the shebang still works.
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return { name: valid, binDir: resolved, paths: aliasFiles(resolved, valid, platform) };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export async function removeAlias(name, { binDir, platform = process.platform, statImpl = stat, rmImpl = rm } = {}) {
|
|
180
|
-
const valid = validateAliasName(name);
|
|
181
|
-
if (valid === 'veo') throw new Error('Refusing to remove veo itself. Remove veodl or a custom wrapper instead.');
|
|
182
|
-
const resolved = await resolveBinDir({ binDir, platform });
|
|
183
|
-
const status = await aliasStatus(valid, { binDir: resolved, platform, statImpl });
|
|
184
|
-
if (!status.present) throw new Error(`${valid} is not installed in ${resolved}.`);
|
|
185
|
-
for (const file of status.paths) await rmImpl(file, { force: true });
|
|
186
|
-
return { name: valid, binDir: resolved, removed: status.paths, builtin: status.builtin };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function parseAliasArgs(args) {
|
|
190
|
-
let binDir;
|
|
191
|
-
let force = false;
|
|
192
|
-
let json = false;
|
|
193
|
-
const rest = [];
|
|
194
|
-
for (let index = 0; index < args.length; index++) {
|
|
195
|
-
const token = args[index];
|
|
196
|
-
if (token === '--force') force = true;
|
|
197
|
-
else if (token === '--json') json = true;
|
|
198
|
-
else if (token === '--bin-dir') {
|
|
199
|
-
binDir = args[++index];
|
|
200
|
-
if (!binDir) throw new Error('--bin-dir requires a directory. Run veo alias --help for usage.');
|
|
201
|
-
} else if (token.startsWith('--bin-dir=')) {
|
|
202
|
-
binDir = token.slice(10) || undefined;
|
|
203
|
-
if (!binDir) throw new Error('--bin-dir requires a directory. Run veo alias --help for usage.');
|
|
204
|
-
} else rest.push(token);
|
|
205
|
-
}
|
|
206
|
-
return { binDir, force, json, rest };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export async function aliasMain(args = [], { stdout = process.stdout, stderr = process.stderr, ...options } = {}) {
|
|
210
|
-
[args, stdout, stderr] = commandOutput(args, stdout, stderr);
|
|
211
|
-
const { binDir, force, json, rest } = parseAliasArgs(args);
|
|
212
|
-
const [command, name, ...extra] = rest;
|
|
213
|
-
if (!command || command === 'list') {
|
|
214
|
-
if (name || extra.length) throw new Error('Usage: veo alias list [--json] (or veo alias --help)');
|
|
215
|
-
if (command === 'list' && extra.length) throw new Error('Usage: veo alias list [--json] (or veo alias --help)');
|
|
216
|
-
const result = await listAliases({ ...options, binDir });
|
|
217
|
-
if (json) {
|
|
218
|
-
stdout.write(`${JSON.stringify(result)}\n`);
|
|
219
|
-
return 0;
|
|
220
|
-
}
|
|
221
|
-
stdout.write(`Command directory: ${result.binDir}\n`);
|
|
222
|
-
for (const entry of result.aliases) {
|
|
223
|
-
const kind = entry.builtin ? 'shipped' : 'custom';
|
|
224
|
-
const state = entry.present ? (entry.managed ? 'wrapper -> veo' : 'installed') : 'missing';
|
|
225
|
-
stdout.write(` ${entry.name} (${kind}, ${state})\n`);
|
|
226
|
-
}
|
|
227
|
-
const missing = result.aliases.filter(entry => !entry.present).map(entry => entry.name);
|
|
228
|
-
if (missing.length) stdout.write(`Missing: ${missing.join(', ')} (veo update recreates shipped names).\n`);
|
|
229
|
-
return 0;
|
|
230
|
-
}
|
|
231
|
-
if (command === '-h' || command === '--help') {
|
|
232
|
-
stdout.write(ALIAS_HELP);
|
|
233
|
-
return 0;
|
|
234
|
-
}
|
|
235
|
-
if (command === 'add') {
|
|
236
|
-
if (!name || extra.length) throw new Error('Usage: veo alias add <name> [--force] (or veo alias --help)');
|
|
237
|
-
const created = await addAlias(name, { ...options, binDir, force });
|
|
238
|
-
if (json) {
|
|
239
|
-
stdout.write(`${JSON.stringify({ status: 'added', ...created })}\n`);
|
|
240
|
-
return 0;
|
|
241
|
-
}
|
|
242
|
-
stdout.write(`Alias installed: ${created.name} -> veo (${created.binDir})\n`);
|
|
243
|
-
stdout.write(`Run "${created.name} --help" to use it. Remove it with: veo alias remove ${created.name}\n`);
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
if (command === 'remove' || command === 'rm' || command === 'uninstall') {
|
|
247
|
-
if (!name || extra.length) throw new Error('Usage: veo alias remove <name> (or veo alias --help)');
|
|
248
|
-
const removed = await removeAlias(name, { ...options, binDir });
|
|
249
|
-
if (json) {
|
|
250
|
-
stdout.write(`${JSON.stringify({ status: 'removed', ...removed })}\n`);
|
|
251
|
-
return 0;
|
|
252
|
-
}
|
|
253
|
-
stdout.write(`Alias removed: ${removed.name} (${removed.removed.length} file(s) in ${removed.binDir})\n`);
|
|
254
|
-
if (removed.builtin) stdout.write('Note: veo update recreates the shipped veodl name.\n');
|
|
255
|
-
return 0;
|
|
256
|
-
}
|
|
257
|
-
throw new Error(`Unknown alias command: ${cleanText(command)}. Use: veo alias list | veo alias add <name> | veo alias remove <name>`);
|
|
258
|
-
}
|
package/src/backend-update.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { commandOutput } from './output.js';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { rm } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
5
|
-
RELEASE, activeOverride, backendCacheDirectory, clearBackendOverride, installBackend,
|
|
6
|
-
isValidRelease, readBackendOverride, releaseUrl, selectAsset, writeBackendOverride,
|
|
7
|
-
} from './backend.js';
|
|
8
|
-
import { compareVersions } from './version.js';
|
|
9
|
-
import { readableError, cleanText } from './utils.js';
|
|
10
|
-
|
|
11
|
-
const API_LATEST = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest';
|
|
12
|
-
const CHECKSUMS = 'SHA2-256SUMS';
|
|
13
|
-
|
|
14
|
-
export const BACKEND_HELP = `veo backend - manage the yt-dlp backend
|
|
15
|
-
|
|
16
|
-
Usage:
|
|
17
|
-
veo backend update Install the newest yt-dlp release
|
|
18
|
-
veo backend update --check Only report whether a newer release exists
|
|
19
|
-
veo backend reset Forget the installed release and use the pinned one
|
|
20
|
-
veo backend reset --keep-files
|
|
21
|
-
Remove the pointer but keep the downloaded files
|
|
22
|
-
veo backend --help Show this help
|
|
23
|
-
|
|
24
|
-
veo ships a pinned, hash-verified yt-dlp release. \`veo backend update\` is an
|
|
25
|
-
opt-in escape hatch for when a website changed and the pinned release is too old:
|
|
26
|
-
it installs the newest official release after verifying its SHA-256 against that
|
|
27
|
-
release's own SHA2-256SUMS file over HTTPS.
|
|
28
|
-
|
|
29
|
-
That checksum does not ship with veo, so this weakens the pinned-hash guarantee:
|
|
30
|
-
it then rests on HTTPS and GitHub alone. \`veo backend reset\` returns to the
|
|
31
|
-
release veo was built and tested against. VEO_YT_DLP_PATH always wins over both.
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
export async function fetchLatestBackendRelease({ fetchImpl = fetch, timeoutMs = 10_000, signal } = {}) {
|
|
35
|
-
const response = await fetchImpl(API_LATEST, {
|
|
36
|
-
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
|
|
37
|
-
headers: { accept: 'application/vnd.github+json', 'user-agent': 'veo' },
|
|
38
|
-
});
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
await response.body?.cancel();
|
|
41
|
-
throw new Error(`Could not query the latest yt-dlp release: HTTP ${response.status}.`);
|
|
42
|
-
}
|
|
43
|
-
const data = await response.json();
|
|
44
|
-
const tag = data?.tag_name;
|
|
45
|
-
if (!isValidRelease(tag)) throw new Error(`The release host returned an unexpected version: ${cleanText(String(tag))}`);
|
|
46
|
-
return tag;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Parse an official SHA2-256SUMS file into a name -> hash map. */
|
|
50
|
-
export function parseChecksums(text) {
|
|
51
|
-
const hashes = new Map();
|
|
52
|
-
for (const line of String(text).split('\n')) {
|
|
53
|
-
const match = /^([a-f0-9]{64})\s+\*?(.+?)\s*$/i.exec(line.trim());
|
|
54
|
-
if (match) hashes.set(path.basename(match[2]), match[1].toLowerCase());
|
|
55
|
-
}
|
|
56
|
-
if (!hashes.size) throw new Error('The release checksum list could not be parsed.');
|
|
57
|
-
return hashes;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function fetchReleaseChecksum({ release, asset, fetchImpl = fetch, timeoutMs = 15_000, signal }) {
|
|
61
|
-
if (!isValidRelease(release)) throw new Error(`Invalid yt-dlp release: ${release}`);
|
|
62
|
-
const response = await fetchImpl(releaseUrl(release, CHECKSUMS), {
|
|
63
|
-
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
|
|
64
|
-
});
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
await response.body?.cancel();
|
|
67
|
-
throw new Error(`Could not download ${CHECKSUMS} for yt-dlp ${release}: HTTP ${response.status}.`);
|
|
68
|
-
}
|
|
69
|
-
const hash = parseChecksums(await response.text()).get(asset);
|
|
70
|
-
if (!hash) throw new Error(`yt-dlp ${release} does not publish a checksum for ${asset}.`);
|
|
71
|
-
return hash;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function muslDetected() {
|
|
75
|
-
return process.platform === 'linux' && !process.report.getReport().header.glibcVersionRuntime;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function checkBackend({
|
|
79
|
-
platform = process.platform,
|
|
80
|
-
arch = process.arch,
|
|
81
|
-
musl = muslDetected(),
|
|
82
|
-
signal,
|
|
83
|
-
stateFile,
|
|
84
|
-
fetchImpl = fetch,
|
|
85
|
-
installed,
|
|
86
|
-
latest,
|
|
87
|
-
} = {}) {
|
|
88
|
-
const asset = selectAsset(platform, arch, musl);
|
|
89
|
-
const active = installed !== undefined ? installed : await activeOverride(asset, { signal, stateFile });
|
|
90
|
-
return {
|
|
91
|
-
asset,
|
|
92
|
-
active,
|
|
93
|
-
current: active?.release ?? RELEASE,
|
|
94
|
-
latest: latest ?? await fetchLatestBackendRelease({ fetchImpl, signal }),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function backendUpdateMain(args = [], {
|
|
99
|
-
stdout = process.stdout,
|
|
100
|
-
stderr = process.stderr,
|
|
101
|
-
env = process.env,
|
|
102
|
-
signal,
|
|
103
|
-
stateFile,
|
|
104
|
-
platform = process.platform,
|
|
105
|
-
arch = process.arch,
|
|
106
|
-
fetchImpl = fetch,
|
|
107
|
-
check = checkBackend,
|
|
108
|
-
install = installBackend,
|
|
109
|
-
reset = clearBackendOverride,
|
|
110
|
-
removeFiles = directory => rm(directory, { recursive: true, force: true }),
|
|
111
|
-
} = {}) {
|
|
112
|
-
[args, stdout, stderr] = commandOutput(args, stdout, stderr);
|
|
113
|
-
const [command, ...rest] = args;
|
|
114
|
-
if (command === undefined || command === '-h' || command === '--help' || rest.includes('-h') || rest.includes('--help')) {
|
|
115
|
-
stdout.write(BACKEND_HELP);
|
|
116
|
-
return 0;
|
|
117
|
-
}
|
|
118
|
-
if (command === 'reset') {
|
|
119
|
-
const unknown = rest.filter(token => token !== '--keep-files');
|
|
120
|
-
if (unknown.length) throw new Error(`Unknown option for veo backend reset: ${unknown.join(' ')}. Use: veo backend reset [--keep-files]`);
|
|
121
|
-
if (platform === 'android') {
|
|
122
|
-
stdout.write('Android/Termux uses system yt-dlp or VEO_YT_DLP_PATH. There is no managed backend to reset.\n');
|
|
123
|
-
return 0;
|
|
124
|
-
}
|
|
125
|
-
const state = await readBackendOverride(stateFile);
|
|
126
|
-
if (!await reset(stateFile)) {
|
|
127
|
-
stdout.write(`No installed backend to remove. veo uses the pinned release ${RELEASE}.\n`);
|
|
128
|
-
return 0;
|
|
129
|
-
}
|
|
130
|
-
if (state?.release && !rest.includes('--keep-files')) await removeFiles(backendCacheDirectory(state.release)).catch(() => {});
|
|
131
|
-
const removed = state?.release ? ` yt-dlp ${state.release}` : ' the installed backend';
|
|
132
|
-
stdout.write(`Removed${removed}. veo uses the pinned release ${RELEASE} again.\n`);
|
|
133
|
-
return 0;
|
|
134
|
-
}
|
|
135
|
-
if (command !== 'update') {
|
|
136
|
-
throw new Error(`Unknown backend command: ${cleanText(command)}. Use: veo backend update [--check] | veo backend reset`);
|
|
137
|
-
}
|
|
138
|
-
const checkOnly = rest.includes('--check');
|
|
139
|
-
const unknown = rest.filter(token => token !== '--check');
|
|
140
|
-
if (unknown.length) throw new Error(`Unknown option for veo backend update: ${unknown.join(' ')}. Use: veo backend update [--check]`);
|
|
141
|
-
if (platform === 'android') {
|
|
142
|
-
stdout.write('Android/Termux uses system yt-dlp or VEO_YT_DLP_PATH, not a pinned release. Update the Termux package with: pkg upgrade python-yt-dlp yt-dlp-ejs\n');
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
145
|
-
if (env.VEO_YT_DLP_PATH) stderr.write('veo: VEO_YT_DLP_PATH is set and always takes precedence over the managed backend.\n');
|
|
146
|
-
|
|
147
|
-
let info;
|
|
148
|
-
try {
|
|
149
|
-
info = await check({ platform, arch, signal, stateFile, fetchImpl });
|
|
150
|
-
} catch (error) {
|
|
151
|
-
stderr.write(`veo: ${readableError(error)}\n`);
|
|
152
|
-
return 1;
|
|
153
|
-
}
|
|
154
|
-
const { asset, current, latest } = info;
|
|
155
|
-
if (!asset) {
|
|
156
|
-
stderr.write(`veo: no standalone yt-dlp is published for ${platform}/${arch}. Set VEO_YT_DLP_PATH to a trusted executable.\n`);
|
|
157
|
-
return 1;
|
|
158
|
-
}
|
|
159
|
-
if (compareVersions(latest, current) <= 0) {
|
|
160
|
-
stdout.write(`yt-dlp ${current} is up to date${current === RELEASE ? ' (pinned release, hash verified at build time)' : ' (installed release)'}.\n`);
|
|
161
|
-
return 0;
|
|
162
|
-
}
|
|
163
|
-
if (checkOnly) {
|
|
164
|
-
stdout.write(`Newer yt-dlp release available: ${latest} (using ${current}). Run: veo backend update\n`);
|
|
165
|
-
return 0;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
let sha256;
|
|
169
|
-
try {
|
|
170
|
-
sha256 = await fetchReleaseChecksum({ release: latest, asset, fetchImpl, signal });
|
|
171
|
-
} catch (error) {
|
|
172
|
-
stderr.write(`veo: ${readableError(error)}\nNothing was installed. The pinned release ${RELEASE} is still in use.\n`);
|
|
173
|
-
return 1;
|
|
174
|
-
}
|
|
175
|
-
stdout.write(`Installing yt-dlp ${latest} for ${platform}/${asset}…\n`);
|
|
176
|
-
try {
|
|
177
|
-
await install({ release: latest, asset, sha256, signal, status: message => stdout.write(`${message}\n`) });
|
|
178
|
-
} catch (error) {
|
|
179
|
-
stderr.write(`veo: ${readableError(error)}\nThe pinned release ${RELEASE} is still in use.\n`);
|
|
180
|
-
return 1;
|
|
181
|
-
}
|
|
182
|
-
try {
|
|
183
|
-
await writeBackendOverride({ release: latest, asset, sha256, platform, arch, installed: new Date().toISOString() }, stateFile);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
stderr.write(`veo: ${readableError(error)}\nThe download succeeded, but veo could not record it; the pinned release stays in use.\n`);
|
|
186
|
-
return 1;
|
|
187
|
-
}
|
|
188
|
-
stdout.write(`yt-dlp ${latest} installed and will be used for the next download.\n`);
|
|
189
|
-
stderr.write(`veo: note: yt-dlp ${latest} was verified against the checksum published with that release over HTTPS, not against a hash shipped with veo. Run "veo backend reset" to return to the pinned release.\n`);
|
|
190
|
-
return 0;
|
|
191
|
-
}
|