@koda-sl/baker-cli 0.114.0-dev.249eaa8ed → 0.115.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/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  generateCatalog,
13
13
  resolveConcurrency,
14
14
  validateCanvasDeep
15
- } from "./chunk-5AWO4BHJ.js";
15
+ } from "./chunk-ZWMMCLJ4.js";
16
16
 
17
17
  // src/cli.ts
18
18
  import { defineCommand as defineCommand157, runMain } from "citty";
@@ -4226,7 +4226,11 @@ var responsiveDisplayAdSchema = z8.object({
4226
4226
  longHeadline: z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.longHeadlineTextMax) }),
4227
4227
  descriptions: z8.array(z8.object({ text: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.descriptionTextMax) })).min(1).max(5),
4228
4228
  businessName: z8.string().min(1).max(GOOGLE_ADS_LIMITS.responsiveDisplayAd.businessNameMax),
4229
+ // A Responsive Display Ad's images are fields on the ad's own content (never campaign-level
4230
+ // asset links). Google requires ≥1 landscape marketing image (1.91:1) AND ≥1 square marketing
4231
+ // image (1:1) to serve; the logo images are optional.
4229
4232
  marketingImageAssets: z8.array(refSchema).optional(),
4233
+ squareMarketingImageAssets: z8.array(refSchema).optional(),
4230
4234
  logoImageAssets: z8.array(refSchema).optional(),
4231
4235
  finalUrls: z8.array(httpsUrlSchema2).min(1)
4232
4236
  });
@@ -4411,7 +4415,11 @@ var adScheduleCriterionSchema = z8.object({
4411
4415
  var deviceCriterionSchema = z8.object({
4412
4416
  criterionType: z8.literal("device"),
4413
4417
  device: z8.enum(DEVICE_TYPES),
4414
- bidModifier: z8.number().min(0.1).max(10).optional()
4418
+ // Google's `CampaignCriterion.bid_modifier`: "The modifier must be in the range 0.1 - 10.0. Use 0
4419
+ // to opt out of a Device type." So 0 (exclude the device) and 0.1–10.0 are valid; the (0, 0.1) gap is not.
4420
+ bidModifier: z8.number().min(0).max(10).optional().refine((v) => v === void 0 || v === 0 || v >= 0.1, {
4421
+ message: "bid modifier must be 0 (exclude the device) or between 0.1 and 10.0"
4422
+ })
4415
4423
  });
4416
4424
  var campaignCriterionAddSchema = z8.object({
4417
4425
  campaign: refSchema,
@@ -4422,6 +4430,15 @@ var campaignCriterionAddSchema = z8.object({
4422
4430
  adScheduleCriterionSchema,
4423
4431
  deviceCriterionSchema
4424
4432
  ])
4433
+ }).superRefine((val, ctx) => {
4434
+ const c = val.criterion;
4435
+ if (c.criterionType === "adSchedule" && c.endHour === 24 && c.endMinute !== "ZERO") {
4436
+ ctx.addIssue({
4437
+ code: z8.ZodIssueCode.custom,
4438
+ message: "endHour 24 (midnight) cannot have a non-zero endMinute",
4439
+ path: ["criterion", "endMinute"]
4440
+ });
4441
+ }
4425
4442
  });
4426
4443
  var GOOGLE_DRAFT_OP_KINDS = [
4427
4444
  "google.budget.create",
@@ -4595,7 +4612,8 @@ var googleDraftStatusNodeSchema = z9.lazy(
4595
4612
  operation: googleDraftChangeOperationSchema.optional(),
4596
4613
  existing: z9.boolean(),
4597
4614
  collections: z9.array(googleDraftStatusCollectionSchema),
4598
- children: z9.array(googleDraftStatusNodeSchema)
4615
+ children: z9.array(googleDraftStatusNodeSchema),
4616
+ warnings: z9.array(z9.string()).optional()
4599
4617
  })
4600
4618
  );
4601
4619
  var googleDraftListResponseSchema = z9.object({
@@ -4663,6 +4681,9 @@ function collectionLine(collection) {
4663
4681
  function renderNode(node, depth, lines) {
4664
4682
  const indent = " ".repeat(depth);
4665
4683
  lines.push(`${indent}\u2022 ${node.name} \xB7 ${badge(node)}`);
4684
+ for (const warning of node.warnings ?? []) {
4685
+ lines.push(`${indent} \u26A0 ${warning}`);
4686
+ }
4666
4687
  for (const collection of node.collections) {
4667
4688
  const line = collectionLine(collection);
4668
4689
  if (line) {
@@ -5798,8 +5819,9 @@ function applyAutoFixes(query, limit) {
5798
5819
  if (/FROM\s+campaign_budget\b/i.test(corrected)) {
5799
5820
  const whereClause = corrected.split(/\bWHERE\b/i)[1] ?? "";
5800
5821
  const selectClause = corrected.split(/\bFROM\b/i)[0] ?? "";
5822
+ const selectFields = new Set(selectClause.match(/campaign\.[\w.]+/g) ?? []);
5801
5823
  const missing = [...new Set(whereClause.match(/campaign\.[\w.]+/g) ?? [])].filter(
5802
- (field) => !selectClause.includes(field)
5824
+ (field) => !selectFields.has(field)
5803
5825
  );
5804
5826
  if (missing.length > 0) {
5805
5827
  corrected = corrected.replace(/SELECT\s+/i, `SELECT ${missing.join(", ")}, `);
@@ -6654,6 +6676,52 @@ function rsaContentFromFlags(args) {
6654
6676
  finalUrls
6655
6677
  };
6656
6678
  }
6679
+ function rdaContentFromFlags(args, base) {
6680
+ const content = {
6681
+ format: "responsiveDisplay",
6682
+ ...base !== null && typeof base === "object" && !Array.isArray(base) ? base : {}
6683
+ };
6684
+ const headlines = listFlag(args.headlines);
6685
+ if (headlines) {
6686
+ content.headlines = headlines.map((text) => ({ text }));
6687
+ }
6688
+ const descriptions = listFlag(args.descriptions);
6689
+ if (descriptions) {
6690
+ content.descriptions = descriptions.map((text) => ({ text }));
6691
+ }
6692
+ if (typeof args["long-headline"] === "string") {
6693
+ content.longHeadline = { text: args["long-headline"] };
6694
+ }
6695
+ if (typeof args["business-name"] === "string") {
6696
+ content.businessName = args["business-name"];
6697
+ }
6698
+ const finalUrls = listFlag(args["final-url"]);
6699
+ if (finalUrls) {
6700
+ content.finalUrls = finalUrls;
6701
+ }
6702
+ const marketing = listFlag(args["marketing-images"]);
6703
+ if (marketing) {
6704
+ content.marketingImageAssets = marketing;
6705
+ }
6706
+ const square = listFlag(args["square-marketing-images"]);
6707
+ if (square) {
6708
+ content.squareMarketingImageAssets = square;
6709
+ }
6710
+ const logos = listFlag(args["logo-images"]);
6711
+ if (logos) {
6712
+ content.logoImageAssets = logos;
6713
+ }
6714
+ return content;
6715
+ }
6716
+ function adContentFromFlags(args, format, fileContent) {
6717
+ if (format === "responsiveSearch") {
6718
+ return fileContent ?? rsaContentFromFlags(args);
6719
+ }
6720
+ if (format === "responsiveDisplay") {
6721
+ return rdaContentFromFlags(args, fileContent);
6722
+ }
6723
+ return fileContent ?? failWriteValidation(`--format ${format} needs --file with the ad content`);
6724
+ }
6657
6725
  var adsCommand = defineCommand30({
6658
6726
  meta: { name: "ads", description: "Stage ad create/update/pause/resume/remove" },
6659
6727
  subCommands: {
@@ -6670,10 +6738,21 @@ var adsCommand = defineCommand30({
6670
6738
  type: "string",
6671
6739
  description: "responsiveSearch (default) | responsiveDisplay | performanceMaxAssetGroup | call | app | video | demandGen"
6672
6740
  },
6673
- headlines: { type: "string", description: "Comma-separated headlines (RSA)" },
6674
- descriptions: { type: "string", description: "Comma-separated descriptions (RSA)" },
6741
+ headlines: { type: "string", description: "Comma-separated headlines (RSA/RDA)" },
6742
+ descriptions: { type: "string", description: "Comma-separated descriptions (RSA/RDA)" },
6675
6743
  path1: { type: "string" },
6676
6744
  path2: { type: "string" },
6745
+ "long-headline": { type: "string", description: "Long headline (responsiveDisplay)" },
6746
+ "business-name": { type: "string", description: "Business name (responsiveDisplay)" },
6747
+ "marketing-images": {
6748
+ type: "string",
6749
+ description: "Comma-separated marketing image asset refs (responsiveDisplay)"
6750
+ },
6751
+ "square-marketing-images": {
6752
+ type: "string",
6753
+ description: "Comma-separated square marketing image asset refs (responsiveDisplay)"
6754
+ },
6755
+ "logo-images": { type: "string", description: "Comma-separated logo image asset refs (responsiveDisplay)" },
6677
6756
  "final-url": { type: "string", description: "Comma-separated final URLs" },
6678
6757
  status: { type: "string" }
6679
6758
  },
@@ -6681,7 +6760,7 @@ var adsCommand = defineCommand30({
6681
6760
  const customerId = requireCustomerId(args);
6682
6761
  const file = loadJsonFileArg(args.file);
6683
6762
  const format = args.format ?? "responsiveSearch";
6684
- const content = file.content ?? (format === "responsiveSearch" ? rsaContentFromFlags(args) : failWriteValidation(`--format ${format} needs --file with the ad content`));
6763
+ const content = adContentFromFlags(args, format, file.content);
6685
6764
  await stageCreate("google.ad.create", customerId, {
6686
6765
  adGroup: requireStringFlag(args["ad-group-ref"] ?? file.adGroup, "--ad-group-ref"),
6687
6766
  status: args.status ?? file.status,
@@ -12835,6 +12914,10 @@ var runCommand = defineCommand85({
12835
12914
  type: "string",
12836
12915
  description: "Max nodes per layer in flight at once (default 5; env BAKER_CANVAS_CONCURRENCY)"
12837
12916
  },
12917
+ parallel: {
12918
+ type: "string",
12919
+ description: "Alias for --concurrency: independent clips (and every other same-layer node) already fan out in parallel up to this bound"
12920
+ },
12838
12921
  "keep-runs": {
12839
12922
  type: "string",
12840
12923
  description: "After the run, prune old r_* run dirs, keeping the N newest (off by default)"
@@ -12884,7 +12967,8 @@ var runCommand = defineCommand85({
12884
12967
  run_id: args["run-id"] ? String(args["run-id"]) : void 0,
12885
12968
  cache_policy: policy,
12886
12969
  concurrency: resolveConcurrency(
12887
- args.concurrency !== void 0 ? String(args.concurrency) : void 0,
12970
+ // --concurrency wins; --parallel is the discoverable alias for the same bound.
12971
+ (args.concurrency ?? args.parallel) !== void 0 ? String(args.concurrency ?? args.parallel) : void 0,
12888
12972
  process.env.BAKER_CANVAS_CONCURRENCY
12889
12973
  )
12890
12974
  });
@@ -15844,7 +15928,8 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
15844
15928
  }
15845
15929
  const todo = {
15846
15930
  ...buildVideoTodo(videoReport(input, elementsInput), overlays.length, floating.length, opts, blueprint),
15847
- ...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel)))
15931
+ ...aspectRemapTodo(resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel))),
15932
+ model_constraints: buildModelConstraints()
15848
15933
  };
15849
15934
  return {
15850
15935
  schema: "baker-canvas/1",
@@ -15855,21 +15940,58 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
15855
15940
  // The timing plan `baker canvas validate` checks before any billed render:
15856
15941
  // sequenced voiceover turns (no overlap), audio ≈ video length, and which
15857
15942
  // scenes must be lip-synced.
15858
- video: buildVideoMeta(blueprint, { vo_segments, talking_scenes })
15943
+ video: buildVideoMeta(blueprint, { vo_segments, talking_scenes }, slots, nodes)
15859
15944
  },
15860
15945
  nodes,
15861
15946
  output: { node: videoNode, output: "video" }
15862
15947
  };
15863
15948
  }
15864
- function buildVideoMeta(blueprint, meta) {
15949
+ function buildVideoMeta(blueprint, meta, slots, nodes) {
15865
15950
  return {
15866
15951
  duration_s: blueprint.source?.duration_s ?? lastSceneEnd(blueprint),
15867
15952
  vo_segments: [...meta.vo_segments].sort((a, b) => a.start_s - b.start_s),
15868
15953
  talking_scenes: meta.talking_scenes,
15869
15954
  lip_sync_caution: buildLipSyncCaution(meta.vo_segments),
15870
- motion_board: buildMotionBoard(blueprint)
15955
+ motion_board: buildMotionBoard(blueprint),
15956
+ // The wired recurring-element registry (the reference-completeness check reads
15957
+ // `ref`/`label`/`type` to warn when a frame describes an element it doesn't wire).
15958
+ elements: slots.map((s) => ({
15959
+ ref: s.ref,
15960
+ label: s.label,
15961
+ type: s.type,
15962
+ ...s.description ? { description: s.description } : {}
15963
+ })),
15964
+ clip_spans: buildClipSpans(blueprint, nodes)
15871
15965
  };
15872
15966
  }
15967
+ function buildModelConstraints() {
15968
+ const models = {};
15969
+ for (const [id, spec] of Object.entries(MODEL_REGISTRY.video_generate)) {
15970
+ const p = spec.params;
15971
+ models[id] = {
15972
+ label: spec.label,
15973
+ aspect_ratios: p.aspect_ratio?.enum ?? null,
15974
+ durations_s: p.duration?.enum ?? null,
15975
+ person_generation: p.person_generation?.enum ?? null
15976
+ };
15977
+ }
15978
+ return {
15979
+ note: "Switching a clip's video model has cross-node constraints. ALL video_generate nodes in one canvas MUST share ONE aspect_ratio (the composite silently crops otherwise). Each model's `duration` is a hard enum and differs per model \u2014 a scene longer than the model's max must be split. `person_generation` differs (Veo requires \"allow_all\"). `baker canvas validate` gates duration, aspect agreement, per-model params, AND a scene-span-vs-model-max advisory before any billed run \u2014 run it after every model edit.",
15980
+ models
15981
+ };
15982
+ }
15983
+ function buildClipSpans(blueprint, nodes) {
15984
+ const spans = [];
15985
+ for (const n of nodes) {
15986
+ if (n.type !== "video_generate") continue;
15987
+ const m = n.id.match(/^s(\d+)/);
15988
+ const idx = m ? Number(m[1]) : Number.NaN;
15989
+ const scene = Number.isInteger(idx) ? blueprint.scenes[idx] : void 0;
15990
+ if (!scene) continue;
15991
+ spans.push({ node: n.id, span_s: Math.round(sceneDurationS(scene) * 100) / 100 });
15992
+ }
15993
+ return spans;
15994
+ }
15873
15995
  function buildLipSyncCaution(segments) {
15874
15996
  const out = [];
15875
15997
  const byScene = /* @__PURE__ */ new Map();
@@ -16638,7 +16760,8 @@ var validateCommand = defineCommand89({
16638
16760
  ok: true,
16639
16761
  total_nodes: result.canvas.nodes.length,
16640
16762
  estimated_credits: result.estimatedCredits,
16641
- cost_preview: result.perNodeCredits ?? []
16763
+ cost_preview: result.perNodeCredits ?? [],
16764
+ warnings: result.warnings ?? []
16642
16765
  },
16643
16766
  null,
16644
16767
  2