@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/README.md +28 -0
- package/dist/{chunk-5AWO4BHJ.js → chunk-ZWMMCLJ4.js} +168 -20
- package/dist/chunk-ZWMMCLJ4.js.map +1 -0
- package/dist/cli.js +136 -13
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +14 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5AWO4BHJ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
generateCatalog,
|
|
13
13
|
resolveConcurrency,
|
|
14
14
|
validateCanvasDeep
|
|
15
|
-
} from "./chunk-
|
|
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
|
-
|
|
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) => !
|
|
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 =
|
|
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
|
-
|
|
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
|