@socialized.dev/cli 0.2.0 → 0.3.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 +2 -1
- package/dist/bin.js +60 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ publication. You can also pass an existing `asset_...` id to `--video` or `--cov
|
|
|
45
45
|
|
|
46
46
|
YouTube targets take `--youtube-tags`, `--youtube-category`, `--youtube-language`,
|
|
47
47
|
`--youtube-audio-language`, `--youtube-privacy`, `--youtube-made-for-kids`,
|
|
48
|
-
`--youtube-synthetic-media
|
|
48
|
+
`--youtube-synthetic-media`, `--youtube-publish-at` (schedule the release), `--youtube-caption`
|
|
49
|
+
and `--youtube-playlist`; run
|
|
49
50
|
`socialized.dev publications create --help` for the full list.
|
|
50
51
|
|
|
51
52
|
## Brands
|
package/dist/bin.js
CHANGED
|
@@ -6,7 +6,7 @@ import { basename, extname, join } from "node:path";
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
//#region package.json
|
|
9
|
-
var version = "0.
|
|
9
|
+
var version = "0.3.0";
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/api.ts
|
|
12
12
|
var ApiError = class extends Error {
|
|
@@ -255,9 +255,27 @@ const parseDateTime = (value) => {
|
|
|
255
255
|
if (fields === void 0 || calendar.getUTCFullYear() !== year || calendar.getUTCMonth() !== month - 1 || calendar.getUTCDate() !== day || calendar.getUTCHours() !== hour || calendar.getUTCMinutes() !== minute || calendar.getUTCSeconds() !== second || Number.isNaN(date.getTime())) throw new UsageError(`Not a date-time: ${value} (e.g. 2026-10-01T09:00 or 2026-10-01T09:00Z)`);
|
|
256
256
|
return date.toISOString();
|
|
257
257
|
};
|
|
258
|
-
/**
|
|
259
|
-
*
|
|
258
|
+
/** `--youtube-caption <lang>[:<name>]=<path|asset_id>` — e.g. `en=./subs.srt`, `en:SDH=./sdh.vtt`.
|
|
259
|
+
* Language tags never contain `:` or `=`, so the first of each splits unambiguously. */
|
|
260
|
+
const parseCaption = (value) => {
|
|
261
|
+
const eq = value.indexOf("=");
|
|
262
|
+
const track = eq === -1 ? "" : value.slice(0, eq);
|
|
263
|
+
const source = eq === -1 ? "" : value.slice(eq + 1);
|
|
264
|
+
const colon = track.indexOf(":");
|
|
265
|
+
const language = colon === -1 ? track : track.slice(0, colon);
|
|
266
|
+
const name = colon === -1 ? void 0 : track.slice(colon + 1);
|
|
267
|
+
if (language === "" || source === "") throw new UsageError(`Not a caption: ${value} (e.g. en=./subs.srt or en:SDH=./sdh.vtt)`);
|
|
268
|
+
return {
|
|
269
|
+
language,
|
|
270
|
+
source,
|
|
271
|
+
...name ? { name } : {}
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
/** The YouTube target's `options` bag from the `--youtube-*` flags (captions join later, once their
|
|
275
|
+
* files are uploaded). Only set fields are sent, so the API's defaults (public, not made for kids) and
|
|
276
|
+
* its validation stay the single source of truth. */
|
|
260
277
|
const youtubeOptions = (flags) => ({
|
|
278
|
+
...flags.youtubePlaylist?.length ? { playlistIds: flags.youtubePlaylist } : {},
|
|
261
279
|
...flags.youtubeTags !== void 0 ? { tags: flags.youtubeTags } : {},
|
|
262
280
|
...flags.youtubeCategory !== void 0 ? { categoryId: flags.youtubeCategory } : {},
|
|
263
281
|
...flags.youtubeLanguage !== void 0 ? { defaultLanguage: flags.youtubeLanguage } : {},
|
|
@@ -283,7 +301,11 @@ const resolveBrandId = async (ctx, flagBrand) => {
|
|
|
283
301
|
const uploadAsset = async (ctx, filePath, kind, jsonMode) => {
|
|
284
302
|
const ext = extname(filePath).toLowerCase();
|
|
285
303
|
let contentType = "application/octet-stream";
|
|
286
|
-
if (kind === "
|
|
304
|
+
if (kind === "caption") {
|
|
305
|
+
if (ext === ".srt") contentType = "application/x-subrip";
|
|
306
|
+
else if (ext === ".vtt") contentType = "text/vtt";
|
|
307
|
+
else throw new UsageError(`Caption files must be .srt or .vtt: ${filePath}`);
|
|
308
|
+
} else if (kind === "video") {
|
|
287
309
|
if (ext === ".mp4") contentType = "video/mp4";
|
|
288
310
|
else if (ext === ".mov") contentType = "video/quicktime";
|
|
289
311
|
else if (ext === ".m4v") contentType = "video/mp4";
|
|
@@ -343,12 +365,11 @@ const pollPublication = async (ctx, brandId, pubId, jsonMode) => {
|
|
|
343
365
|
while (true) {
|
|
344
366
|
const result = await ctx.api.getPublication(brandId, pubId);
|
|
345
367
|
const pub = result.publication;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
for (const target of pub.targets) out(ctx, ` - ${target.platform}: ${target.status}${target.error ? ` (${target.error})` : ""}`);
|
|
368
|
+
const log = jsonMode ? err : out;
|
|
369
|
+
log(ctx, `Status: ${pub.status}`);
|
|
370
|
+
for (const target of pub.targets) {
|
|
371
|
+
log(ctx, ` - ${target.platform}: ${target.status}${target.error ? ` (${target.error})` : ""}`);
|
|
372
|
+
for (const warning of target.warnings ?? []) log(ctx, ` warning: ${warning}`);
|
|
352
373
|
}
|
|
353
374
|
if (pub.status === "done" || pub.status === "failed" || pub.status === "partial") return result;
|
|
354
375
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
@@ -364,7 +385,10 @@ const printPublicationSummary = (ctx, pub) => {
|
|
|
364
385
|
out(ctx, `Publication ID: ${pub.id}`);
|
|
365
386
|
out(ctx, `Status: ${pub.status}`);
|
|
366
387
|
out(ctx, "Targets:");
|
|
367
|
-
for (const target of pub.targets)
|
|
388
|
+
for (const target of pub.targets) {
|
|
389
|
+
out(ctx, ` - ${target.platform}: ${target.status}${target.externalUrl ? ` (${target.externalUrl})` : ""}${target.error ? ` - Error: ${target.error}` : ""}`);
|
|
390
|
+
for (const warning of target.warnings ?? []) out(ctx, ` warning: ${warning}`);
|
|
391
|
+
}
|
|
368
392
|
};
|
|
369
393
|
const createCommand = buildCommand({
|
|
370
394
|
docs: { brief: "Create a publication, uploading local video/cover files as needed" },
|
|
@@ -446,6 +470,20 @@ const createCommand = buildCommand({
|
|
|
446
470
|
optional: true,
|
|
447
471
|
brief: "YouTube: disclose realistic altered or synthetic content"
|
|
448
472
|
},
|
|
473
|
+
youtubeCaption: {
|
|
474
|
+
kind: "parsed",
|
|
475
|
+
parse: parseCaption,
|
|
476
|
+
variadic: true,
|
|
477
|
+
optional: true,
|
|
478
|
+
brief: "YouTube: caption track <lang>[:<name>]=<.srt/.vtt path or asset id> (repeatable)"
|
|
479
|
+
},
|
|
480
|
+
youtubePlaylist: {
|
|
481
|
+
kind: "parsed",
|
|
482
|
+
parse: String,
|
|
483
|
+
variadic: true,
|
|
484
|
+
optional: true,
|
|
485
|
+
brief: "YouTube: add the video to this playlist id (repeatable)"
|
|
486
|
+
},
|
|
449
487
|
brand: {
|
|
450
488
|
kind: "parsed",
|
|
451
489
|
parse: String,
|
|
@@ -470,7 +508,17 @@ const createCommand = buildCommand({
|
|
|
470
508
|
async func(flags) {
|
|
471
509
|
requireAuth(this);
|
|
472
510
|
const youtube = youtubeOptions(flags);
|
|
473
|
-
|
|
511
|
+
const captionFlags = flags.youtubeCaption ?? [];
|
|
512
|
+
if ((Object.keys(youtube).length > 0 || captionFlags.length > 0) && !flags.to.includes("youtube")) throw new UsageError("`--youtube-*` flags need `youtube` in `--to`.");
|
|
513
|
+
const captions = [];
|
|
514
|
+
for (const { source, ...track } of captionFlags) {
|
|
515
|
+
const assetId = source.startsWith("asset_") ? source : await uploadAsset(this, source, "caption", flags.json);
|
|
516
|
+
captions.push({
|
|
517
|
+
assetId,
|
|
518
|
+
...track
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
if (captions.length > 0) youtube.captions = captions;
|
|
474
522
|
const brandId = await resolveBrandId(this, flags.brand);
|
|
475
523
|
let videoAssetId;
|
|
476
524
|
if (flags.video.startsWith("asset_")) videoAssetId = flags.video;
|