@feralfile/cli 2.10.1 → 4.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/README.md +31 -23
- package/config.json.example +6 -1
- package/dist/index.js +10 -2
- package/dist/package.json +5 -2
- package/dist/src/commands/build.js +40 -0
- package/dist/src/commands/config.js +1 -0
- package/dist/src/commands/device.js +85 -40
- package/dist/src/commands/enrich.js +18 -23
- package/dist/src/commands/find.js +144 -106
- package/dist/src/commands/helpers/device-control.js +937 -0
- package/dist/src/commands/helpers/hidden-alias-help.js +37 -0
- package/dist/src/commands/helpers/playlist-display.js +2 -15
- package/dist/src/commands/helpers/prompt.js +85 -10
- package/dist/src/commands/play.js +162 -84
- package/dist/src/commands/report.js +251 -0
- package/dist/src/commands/setup.js +82 -7
- package/dist/src/commands/status.js +4 -1
- package/dist/src/config.js +256 -6
- package/dist/src/main.js +14 -5
- package/dist/src/utilities/control/codec.js +578 -0
- package/dist/src/utilities/control/control-errors.js +157 -0
- package/dist/src/utilities/control/controller-store.js +964 -0
- package/dist/src/utilities/control/jwk.js +141 -0
- package/dist/src/utilities/control/pairing-channel.js +52 -0
- package/dist/src/utilities/control/pairing.js +416 -0
- package/dist/src/utilities/control/registration.js +391 -0
- package/dist/src/utilities/control/relay-pairing-channel.js +191 -0
- package/dist/src/utilities/control/secure-transport.js +850 -0
- package/dist/src/utilities/control/strict-json.js +376 -0
- package/dist/src/utilities/device-lookup.js +36 -11
- package/dist/src/utilities/device-network.js +129 -0
- package/dist/src/utilities/device-upsert.js +1 -1
- package/dist/src/utilities/feral-file-artwork.js +2 -2
- package/dist/src/utilities/ff1-compatibility.js +87 -11
- package/dist/src/utilities/ff1-device.js +161 -78
- package/dist/src/utilities/ff1-relayer.js +34 -14
- package/dist/src/utilities/handoff-client.js +23 -9
- package/dist/src/utilities/index.js +270 -259
- package/dist/src/utilities/media-url.js +117 -0
- package/dist/src/utilities/neort-marketplace.js +3 -3
- package/dist/src/utilities/nft-indexer.js +12 -376
- package/dist/src/utilities/playlist-builder.js +11 -2
- package/dist/src/utilities/playlist-cast.js +6 -6
- package/dist/src/utilities/playlist-source.js +223 -34
- package/dist/src/utilities/raster-client.js +101 -14
- package/dist/src/utilities/relay-client.js +774 -0
- package/dist/src/utilities/ssh-access.js +192 -58
- package/dist/src/utilities/support-report.js +511 -0
- package/dist/src/utilities/temporary-channel.js +139 -0
- package/dist/src/utilities/token-item.js +428 -0
- package/dist/src/utilities/token-source-contract.js +55 -0
- package/dist/src/utilities/token-source-indexer.js +99 -0
- package/dist/src/utilities/token-source-raster.js +378 -0
- package/dist/src/utilities/token-source.js +48 -0
- package/docs/CONFIGURATION.md +331 -28
- package/docs/EXAMPLES.md +33 -33
- package/docs/PROJECT_SPEC.md +55 -36
- package/docs/README.md +145 -43
- package/package.json +5 -2
package/dist/src/config.js
CHANGED
|
@@ -11,6 +11,8 @@ exports.getPlaylistConfig = getPlaylistConfig;
|
|
|
11
11
|
exports.getFeedConfig = getFeedConfig;
|
|
12
12
|
exports.getFF1DeviceConfig = getFF1DeviceConfig;
|
|
13
13
|
exports.getFF1RelayerConfig = getFF1RelayerConfig;
|
|
14
|
+
exports.getSupportLogsConfig = getSupportLogsConfig;
|
|
15
|
+
exports.getTokenSourceConfig = getTokenSourceConfig;
|
|
14
16
|
exports.validateConfig = validateConfig;
|
|
15
17
|
exports.createSampleConfig = createSampleConfig;
|
|
16
18
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -18,6 +20,45 @@ const path_1 = __importDefault(require("path"));
|
|
|
18
20
|
const os_1 = __importDefault(require("os"));
|
|
19
21
|
const playlist_signing_role_1 = require("./utilities/playlist-signing-role");
|
|
20
22
|
const config_placeholders_1 = require("./utilities/config-placeholders");
|
|
23
|
+
/**
|
|
24
|
+
* The feed server used when neither config.json nor FEED_BASE_URLS names one. It replaced
|
|
25
|
+
* dp1-feed-operator-api-prod.autonomy-system.workers.dev, a deleted worker whose zone belongs to
|
|
26
|
+
* an account being retired: a default must never name a host someone else could register.
|
|
27
|
+
* DEFAULT_FF1_RELAYER_URL below is in the same zone but still live; the ff-cli release after
|
|
28
|
+
* the legacy relayer shutdown turns it off (ffos-user docs/ff1-control-v1.md §15.4).
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_FEED_BASE_URL = 'https://feed.feralfile.com/api/v1';
|
|
31
|
+
/**
|
|
32
|
+
* The deleted worker ff-cli 3.0.0 named. Its `setup` and `config init` copied it into every
|
|
33
|
+
* config.json they wrote, and an explicit feed wins over the default, so a configured URL on
|
|
34
|
+
* this host is read as feed.feralfile.com (path kept) instead: same operator, same API.
|
|
35
|
+
*/
|
|
36
|
+
const RETIRED_FEED_HOST = 'dp1-feed-operator-api-prod.autonomy-system.workers.dev';
|
|
37
|
+
const RETIRED_FEED_REPLACEMENT_ORIGIN = 'https://feed.feralfile.com';
|
|
38
|
+
let retiredFeedNoted = false;
|
|
39
|
+
/**
|
|
40
|
+
* replaceRetiredFeedUrl maps a feed URL on RETIRED_FEED_HOST to feed.feralfile.com, keeping its
|
|
41
|
+
* path, and notes it once per run on stderr (stdout may be a script's JSON). Any other URL,
|
|
42
|
+
* including an unparseable one, is returned unchanged.
|
|
43
|
+
*/
|
|
44
|
+
function replaceRetiredFeedUrl(url) {
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = new URL(url);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return url;
|
|
51
|
+
}
|
|
52
|
+
if (parsed.hostname.toLowerCase() !== RETIRED_FEED_HOST) {
|
|
53
|
+
return url;
|
|
54
|
+
}
|
|
55
|
+
const replaced = `${RETIRED_FEED_REPLACEMENT_ORIGIN}${parsed.pathname.replace(/\/$/, '')}${parsed.search}`;
|
|
56
|
+
if (!retiredFeedNoted) {
|
|
57
|
+
retiredFeedNoted = true;
|
|
58
|
+
console.error(`Note: ${url} is a retired feed server; using ${replaced}. Update the feed in config.json or FEED_BASE_URLS to stop this note.`);
|
|
59
|
+
}
|
|
60
|
+
return replaced;
|
|
61
|
+
}
|
|
21
62
|
function getConfigPaths() {
|
|
22
63
|
const localPath = path_1.default.join(process.cwd(), 'config.json');
|
|
23
64
|
const configBase = process.env.XDG_CONFIG_HOME || path_1.default.join(os_1.default.homedir(), '.config');
|
|
@@ -43,7 +84,7 @@ function loadConfig() {
|
|
|
43
84
|
feed: {
|
|
44
85
|
baseURLs: process.env.FEED_BASE_URLS
|
|
45
86
|
? process.env.FEED_BASE_URLS.split(',')
|
|
46
|
-
: [
|
|
87
|
+
: [DEFAULT_FEED_BASE_URL],
|
|
47
88
|
},
|
|
48
89
|
};
|
|
49
90
|
// Try to load config.json if it exists
|
|
@@ -188,10 +229,13 @@ function getFeedConfig() {
|
|
|
188
229
|
const config = getConfig();
|
|
189
230
|
// Check for new feedServers format first
|
|
190
231
|
if (config.feedServers && Array.isArray(config.feedServers) && config.feedServers.length > 0) {
|
|
191
|
-
const
|
|
232
|
+
const servers = config.feedServers.map((server) => ({
|
|
233
|
+
...server,
|
|
234
|
+
baseUrl: replaceRetiredFeedUrl(server.baseUrl),
|
|
235
|
+
}));
|
|
192
236
|
return {
|
|
193
|
-
baseURLs,
|
|
194
|
-
servers
|
|
237
|
+
baseURLs: servers.map((server) => server.baseUrl),
|
|
238
|
+
servers,
|
|
195
239
|
};
|
|
196
240
|
}
|
|
197
241
|
// Fall back to legacy feed format
|
|
@@ -206,10 +250,10 @@ function getFeedConfig() {
|
|
|
206
250
|
}
|
|
207
251
|
else {
|
|
208
252
|
// Default feed URL
|
|
209
|
-
urls = [
|
|
253
|
+
urls = [DEFAULT_FEED_BASE_URL];
|
|
210
254
|
}
|
|
211
255
|
return {
|
|
212
|
-
baseURLs: urls,
|
|
256
|
+
baseURLs: urls.map(replaceRetiredFeedUrl),
|
|
213
257
|
};
|
|
214
258
|
}
|
|
215
259
|
/**
|
|
@@ -230,6 +274,12 @@ function getFF1DeviceConfig() {
|
|
|
230
274
|
};
|
|
231
275
|
}
|
|
232
276
|
const DEFAULT_FF1_RELAYER_URL = 'https://tv-cast-coordination.autonomy-system.workers.dev';
|
|
277
|
+
/**
|
|
278
|
+
* The production relay v2 origin. Relay v2 runs on its own Worker, not on the
|
|
279
|
+
* legacy relayer's origin, and must keep working after the legacy host is
|
|
280
|
+
* retired (ffos-user docs/ff1-control-v1.md §15.3, §15.4).
|
|
281
|
+
*/
|
|
282
|
+
const DEFAULT_FF1_RELAY_V2_ENDPOINT = 'wss://relayer.feralfile.io';
|
|
233
283
|
/**
|
|
234
284
|
* Get the FF1 relayer endpoint using repository config priority.
|
|
235
285
|
*
|
|
@@ -270,12 +320,200 @@ function resolveFF1RelayerConfig(config) {
|
|
|
270
320
|
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
271
321
|
throw new Error('ff1Relayer.baseUrl must be a valid HTTP(S) URL');
|
|
272
322
|
}
|
|
323
|
+
const v2 = resolveV2Endpoint(fields.v2Endpoint, parsed);
|
|
273
324
|
const apiKey = (fields.apiKey || process.env.FF1_RELAYER_API_KEY)?.trim();
|
|
274
325
|
return {
|
|
275
326
|
baseUrl,
|
|
276
327
|
apiKey: apiKey || undefined,
|
|
328
|
+
...(v2.endpoint ? { v2Endpoint: v2.endpoint } : {}),
|
|
329
|
+
...(v2.endpoint && v2.explicit ? { v2Explicit: true } : {}),
|
|
277
330
|
};
|
|
278
331
|
}
|
|
332
|
+
const DEFAULT_SUPPORT_LOGS_URL = 'https://support-logs.feralfile.com';
|
|
333
|
+
/**
|
|
334
|
+
* getSupportLogsConfig returns the support-logs origin `ff-cli report` posts to.
|
|
335
|
+
*
|
|
336
|
+
* `config.json` (`supportLogs.baseUrl`) wins over FF_SUPPORT_LOGS_URL, which
|
|
337
|
+
* wins over the production default, the same order as `ff1Relayer.baseUrl`.
|
|
338
|
+
* The override is for tests and staging. It does not move the Art Computer's
|
|
339
|
+
* log upload: FF OS posts its logs to the support-logs its firmware names.
|
|
340
|
+
*/
|
|
341
|
+
function getSupportLogsConfig() {
|
|
342
|
+
return resolveSupportLogsConfig(getConfig());
|
|
343
|
+
}
|
|
344
|
+
/** Resolve and validate the effective support-logs configuration. */
|
|
345
|
+
function resolveSupportLogsConfig(config) {
|
|
346
|
+
const configured = config.supportLogs;
|
|
347
|
+
if (configured !== undefined &&
|
|
348
|
+
(configured === null || typeof configured !== 'object' || Array.isArray(configured))) {
|
|
349
|
+
throw new Error('supportLogs must be an object');
|
|
350
|
+
}
|
|
351
|
+
const baseUrl = configured?.baseUrl;
|
|
352
|
+
if (baseUrl !== undefined && typeof baseUrl !== 'string') {
|
|
353
|
+
throw new Error('supportLogs.baseUrl must be a string');
|
|
354
|
+
}
|
|
355
|
+
const value = (baseUrl ||
|
|
356
|
+
process.env.FF_SUPPORT_LOGS_URL ||
|
|
357
|
+
DEFAULT_SUPPORT_LOGS_URL)
|
|
358
|
+
.trim()
|
|
359
|
+
.replace(/\/+$/, '');
|
|
360
|
+
let parsed;
|
|
361
|
+
try {
|
|
362
|
+
parsed = new URL(value);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
throw new Error('supportLogs.baseUrl must be a valid HTTP(S) URL');
|
|
366
|
+
}
|
|
367
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
368
|
+
throw new Error('supportLogs.baseUrl must be a valid HTTP(S) URL');
|
|
369
|
+
}
|
|
370
|
+
return { baseUrl: value };
|
|
371
|
+
}
|
|
372
|
+
const TOKEN_SOURCES = ['raster', 'indexer'];
|
|
373
|
+
const DEFAULT_TOKEN_SOURCE = 'indexer';
|
|
374
|
+
const DEFAULT_IPFS_GATEWAY_FERAL_FILE = 'https://ipfs.feralfile.com/ipfs/';
|
|
375
|
+
const DEFAULT_IPFS_GATEWAY = 'https://ipfs.filebase.io/ipfs/';
|
|
376
|
+
/**
|
|
377
|
+
* Get the token source selection using repository config priority.
|
|
378
|
+
*
|
|
379
|
+
* `config.json` wins over environment variables, which win over the defaults.
|
|
380
|
+
* The Feral File indexer is the default vendor; `raster` is opt-in while
|
|
381
|
+
* Raster's data bugs (feral-file#3546) are open. There is no fallback between
|
|
382
|
+
* the two at runtime: one vendor answers a whole run.
|
|
383
|
+
*/
|
|
384
|
+
function getTokenSourceConfig() {
|
|
385
|
+
return resolveTokenSourceConfig(getConfig());
|
|
386
|
+
}
|
|
387
|
+
/** Resolve and validate the effective token source configuration. */
|
|
388
|
+
function resolveTokenSourceConfig(config) {
|
|
389
|
+
const source = resolveTokenSourceName(config);
|
|
390
|
+
const gateways = resolveIpfsGatewayFields(config);
|
|
391
|
+
return {
|
|
392
|
+
source,
|
|
393
|
+
ipfsGateways: {
|
|
394
|
+
feralFile: normalizeIpfsGateway(gateways.feralFile ||
|
|
395
|
+
process.env.IPFS_GATEWAY_FERAL_FILE ||
|
|
396
|
+
DEFAULT_IPFS_GATEWAY_FERAL_FILE, 'ipfsGateways.feralFile'),
|
|
397
|
+
default: normalizeIpfsGateway(gateways.default || process.env.IPFS_GATEWAY_DEFAULT || DEFAULT_IPFS_GATEWAY, 'ipfsGateways.default'),
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/** resolveTokenSourceName picks the vendor name, rejecting anything outside TOKEN_SOURCES. */
|
|
402
|
+
function resolveTokenSourceName(config) {
|
|
403
|
+
const configured = config.tokenSource;
|
|
404
|
+
if (configured !== undefined && typeof configured !== 'string') {
|
|
405
|
+
throw new Error(`tokenSource must be one of: ${TOKEN_SOURCES.join(', ')}`);
|
|
406
|
+
}
|
|
407
|
+
const candidate = (configured || process.env.FF_TOKEN_SOURCE || '')
|
|
408
|
+
.trim()
|
|
409
|
+
.toLowerCase();
|
|
410
|
+
if (!candidate) {
|
|
411
|
+
return DEFAULT_TOKEN_SOURCE;
|
|
412
|
+
}
|
|
413
|
+
if (!TOKEN_SOURCES.includes(candidate)) {
|
|
414
|
+
throw new Error(`tokenSource must be one of: ${TOKEN_SOURCES.join(', ')}`);
|
|
415
|
+
}
|
|
416
|
+
return candidate;
|
|
417
|
+
}
|
|
418
|
+
/** resolveIpfsGatewayFields type-checks the `ipfsGateways` block without resolving defaults. */
|
|
419
|
+
function resolveIpfsGatewayFields(config) {
|
|
420
|
+
const configured = config.ipfsGateways;
|
|
421
|
+
if (configured === undefined) {
|
|
422
|
+
return {};
|
|
423
|
+
}
|
|
424
|
+
if (configured === null || typeof configured !== 'object' || Array.isArray(configured)) {
|
|
425
|
+
throw new Error('ipfsGateways must be an object');
|
|
426
|
+
}
|
|
427
|
+
const fields = configured;
|
|
428
|
+
for (const key of ['feralFile', 'default']) {
|
|
429
|
+
if (fields[key] !== undefined && typeof fields[key] !== 'string') {
|
|
430
|
+
throw new Error(`ipfsGateways.${key} must be a string`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return { feralFile: fields.feralFile, default: fields.default };
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* normalizeIpfsGateway validates a gateway and returns it in `https://host/ipfs/` form.
|
|
437
|
+
*
|
|
438
|
+
* A bare origin is accepted because that is how gateways are usually quoted;
|
|
439
|
+
* the `/ipfs/` path is what a CID is appended to, so it is added when absent
|
|
440
|
+
* and a trailing slash is guaranteed so callers can concatenate.
|
|
441
|
+
*/
|
|
442
|
+
function normalizeIpfsGateway(value, label) {
|
|
443
|
+
let parsed;
|
|
444
|
+
try {
|
|
445
|
+
parsed = new URL(value.trim());
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
throw new Error(`${label} must be a valid HTTP(S) URL`);
|
|
449
|
+
}
|
|
450
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
451
|
+
throw new Error(`${label} must be a valid HTTP(S) URL`);
|
|
452
|
+
}
|
|
453
|
+
const path = parsed.pathname.replace(/\/+$/, '');
|
|
454
|
+
parsed.pathname = path.endsWith('/ipfs') ? `${path}/` : `${path}/ipfs/`;
|
|
455
|
+
parsed.search = '';
|
|
456
|
+
parsed.hash = '';
|
|
457
|
+
return parsed.toString();
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* resolveV2Endpoint picks the relay v2 origin (ffos-user docs/ff1-control-v1.md
|
|
461
|
+
* §10). An explicit `ff1Relayer.v2Endpoint` or FF1_RELAY_V2_ENDPOINT wins; an
|
|
462
|
+
* empty string turns v2 off. Otherwise the production legacy relayer maps to
|
|
463
|
+
* the production relay v2 Worker (DEFAULT_FF1_RELAY_V2_ENDPOINT), which lives
|
|
464
|
+
* on its own origin. A custom HTTPS legacy relayer (a development Worker that
|
|
465
|
+
* serves both `/api` and `/v2/channels/…`) still uses its own origin. A
|
|
466
|
+
* custom plain-HTTP or path-bearing legacy relayer gets no v2 unless one is
|
|
467
|
+
* configured.
|
|
468
|
+
*
|
|
469
|
+
* The derived default is only a pairing default for commands: during the
|
|
470
|
+
* rollout most FF1s are not on v2, and a stranger's ACK on the device channel
|
|
471
|
+
* would turn a command that the legacy topic could have delivered into an
|
|
472
|
+
* ambiguous one. secure-transport therefore sends commands on a derived
|
|
473
|
+
* endpoint only to an FF1 that advertised `relayV2` in a verified status.
|
|
474
|
+
*
|
|
475
|
+
* @param configured - The `ff1Relayer.v2Endpoint` value, if any
|
|
476
|
+
* @param baseUrl - The validated legacy relayer URL
|
|
477
|
+
* @returns The v2 origin (undefined when v2 is off) and whether it was configured
|
|
478
|
+
* @throws {Error} When a configured endpoint is not an acceptable origin
|
|
479
|
+
*/
|
|
480
|
+
function resolveV2Endpoint(configured, baseUrl) {
|
|
481
|
+
const value = configured ?? process.env.FF1_RELAY_V2_ENDPOINT;
|
|
482
|
+
if (value === undefined) {
|
|
483
|
+
if (baseUrl.origin === new URL(DEFAULT_FF1_RELAYER_URL).origin) {
|
|
484
|
+
return { endpoint: DEFAULT_FF1_RELAY_V2_ENDPOINT, explicit: false };
|
|
485
|
+
}
|
|
486
|
+
const bare = !baseUrl.username && !baseUrl.password && !baseUrl.search && !baseUrl.hash;
|
|
487
|
+
const derived = baseUrl.protocol === 'https:' && bare && ['', '/'].includes(baseUrl.pathname);
|
|
488
|
+
return { endpoint: derived ? baseUrl.origin : undefined, explicit: false };
|
|
489
|
+
}
|
|
490
|
+
if (typeof value !== 'string') {
|
|
491
|
+
throw new Error('ff1Relayer.v2Endpoint must be a string');
|
|
492
|
+
}
|
|
493
|
+
if (value.trim() === '') {
|
|
494
|
+
return { explicit: true };
|
|
495
|
+
}
|
|
496
|
+
let endpoint;
|
|
497
|
+
try {
|
|
498
|
+
endpoint = new URL(value);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
throw new Error('Invalid ff1Relayer.v2Endpoint');
|
|
502
|
+
}
|
|
503
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname);
|
|
504
|
+
if (!['https:', 'wss:'].includes(endpoint.protocol) &&
|
|
505
|
+
!(['http:', 'ws:'].includes(endpoint.protocol) && loopback)) {
|
|
506
|
+
throw new Error('ff1Relayer.v2Endpoint requires WSS/HTTPS outside loopback');
|
|
507
|
+
}
|
|
508
|
+
if (endpoint.username ||
|
|
509
|
+
endpoint.password ||
|
|
510
|
+
endpoint.search ||
|
|
511
|
+
endpoint.hash ||
|
|
512
|
+
!['', '/'].includes(endpoint.pathname)) {
|
|
513
|
+
throw new Error('Invalid ff1Relayer.v2Endpoint');
|
|
514
|
+
}
|
|
515
|
+
return { endpoint: value, explicit: true };
|
|
516
|
+
}
|
|
279
517
|
/**
|
|
280
518
|
* Validate configuration
|
|
281
519
|
*
|
|
@@ -341,6 +579,18 @@ function validateConfig() {
|
|
|
341
579
|
catch (error) {
|
|
342
580
|
errors.push(error.message);
|
|
343
581
|
}
|
|
582
|
+
try {
|
|
583
|
+
resolveTokenSourceConfig(config);
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
errors.push(error.message);
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
resolveSupportLogsConfig(config);
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
errors.push(error.message);
|
|
593
|
+
}
|
|
344
594
|
return {
|
|
345
595
|
valid: errors.length === 0,
|
|
346
596
|
errors,
|
package/dist/src/main.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Builds DP-1 playlists deterministically from structured parameters.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.REQUIREMENT_TYPES = void 0;
|
|
7
8
|
exports.validateRequirements = validateRequirements;
|
|
8
9
|
exports.applyPlaylistDefaults = applyPlaylistDefaults;
|
|
9
10
|
exports.buildPlaylistDirect = buildPlaylistDirect;
|
|
@@ -18,10 +19,20 @@ process.emitWarning = function (warning, type, ctor) {
|
|
|
18
19
|
}
|
|
19
20
|
return originalEmitWarning.apply(this, [warning, type, ctor]);
|
|
20
21
|
};
|
|
21
|
-
const config_1 = require("./config");
|
|
22
22
|
// Lazy load utilities to avoid circular dependencies
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
24
|
const getUtilities = () => require('./utilities');
|
|
25
|
+
/**
|
|
26
|
+
* REQUIREMENT_TYPES lists every requirement `build` accepts, in the order
|
|
27
|
+
* `build --help` documents them. Kept beside validateRequirements so a new
|
|
28
|
+
* branch there and this list change together.
|
|
29
|
+
*/
|
|
30
|
+
exports.REQUIREMENT_TYPES = [
|
|
31
|
+
'build_playlist',
|
|
32
|
+
'query_address',
|
|
33
|
+
'fetch_feed',
|
|
34
|
+
'feral_file_artwork',
|
|
35
|
+
];
|
|
25
36
|
/**
|
|
26
37
|
* Validate and apply constraints to requirements
|
|
27
38
|
*
|
|
@@ -103,7 +114,8 @@ function validateRequirements(requirements) {
|
|
|
103
114
|
};
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
|
-
throw new Error(`Requirement ${index + 1}: invalid type "${req.type}"`
|
|
117
|
+
throw new Error(`Requirement ${index + 1}: invalid type "${req.type}". ` +
|
|
118
|
+
`Valid types: ${exports.REQUIREMENT_TYPES.join(', ')} (see ff-cli build --help).`);
|
|
107
119
|
});
|
|
108
120
|
}
|
|
109
121
|
/**
|
|
@@ -140,8 +152,5 @@ async function buildPlaylistDirect(params, options = {}) {
|
|
|
140
152
|
const requirements = validateRequirements(params.requirements);
|
|
141
153
|
const playlistSettings = applyPlaylistDefaults(params.playlistSettings);
|
|
142
154
|
const utilities = getUtilities();
|
|
143
|
-
const config = (0, config_1.getConfig)();
|
|
144
|
-
// Initialize utilities with config (indexer endpoint, API key, etc.)
|
|
145
|
-
utilities.initializeUtilities(config);
|
|
146
155
|
return await utilities.buildPlaylistDirect({ requirements, playlistSettings }, options);
|
|
147
156
|
}
|