@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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/bin.js +60 -12
  3. 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` and `--youtube-publish-at` (schedule the release); run
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.2.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
- /** The YouTube target's `options` bag from the `--youtube-*` flags. Only set fields are sent, so the
259
- * API's defaults (public, not made for kids) and its validation stay the single source of truth. */
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 === "video") {
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
- if (jsonMode) {
347
- err(ctx, `Status: ${pub.status}`);
348
- for (const target of pub.targets) err(ctx, ` - ${target.platform}: ${target.status}${target.error ? ` (${target.error})` : ""}`);
349
- } else {
350
- out(ctx, `Status: ${pub.status}`);
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) out(ctx, ` - ${target.platform}: ${target.status}${target.externalUrl ? ` (${target.externalUrl})` : ""}${target.error ? ` - Error: ${target.error}` : ""}`);
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
- if (Object.keys(youtube).length > 0 && !flags.to.includes("youtube")) throw new UsageError("`--youtube-*` flags need `youtube` in `--to`.");
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socialized.dev/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",