@koda-sl/baker-cli 0.306.8 → 0.306.9
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 +293 -22
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12194,13 +12194,25 @@ registerSchema({
|
|
|
12194
12194
|
}
|
|
12195
12195
|
});
|
|
12196
12196
|
function extractFields(rows) {
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
return
|
|
12197
|
+
const keys = /* @__PURE__ */ new Set();
|
|
12198
|
+
foldObservedFields(rows, keys);
|
|
12199
|
+
return [...keys];
|
|
12200
|
+
}
|
|
12201
|
+
function resultColumns(gaql, rows) {
|
|
12202
|
+
const observed = extractFields(rows);
|
|
12203
|
+
const columns = [];
|
|
12204
|
+
for (const field of parseSelectedFields(gaql)) {
|
|
12205
|
+
const leaves = observed.filter((key) => key.startsWith(`${field}.`));
|
|
12206
|
+
columns.push(...leaves.length > 0 ? leaves : [field]);
|
|
12207
|
+
}
|
|
12208
|
+
for (const key of observed) columns.push(key);
|
|
12209
|
+
return [...new Set(columns)];
|
|
12210
|
+
}
|
|
12211
|
+
function isCsvPath(filePath) {
|
|
12212
|
+
return filePath.split(".").pop()?.toLowerCase() === "csv";
|
|
12200
12213
|
}
|
|
12201
12214
|
function writeRowsToFile(filePath, rows, fields, append) {
|
|
12202
|
-
|
|
12203
|
-
if (ext === "csv") {
|
|
12215
|
+
if (isCsvPath(filePath)) {
|
|
12204
12216
|
if (!append || !existsSync2(filePath)) {
|
|
12205
12217
|
writeFileSync(filePath, `${toCsvRow(fields)}
|
|
12206
12218
|
`, "utf-8");
|
|
@@ -12208,7 +12220,7 @@ function writeRowsToFile(filePath, rows, fields, append) {
|
|
|
12208
12220
|
const csvRows = rows.map((row) => toCsvRow(fields.map((f) => String(row[f] ?? ""))));
|
|
12209
12221
|
appendFileSync(filePath, `${csvRows.join("\n")}
|
|
12210
12222
|
`, "utf-8");
|
|
12211
|
-
} else if (
|
|
12223
|
+
} else if (filePath.split(".").pop()?.toLowerCase() === "jsonl") {
|
|
12212
12224
|
const lines = rows.map((row) => JSON.stringify(row));
|
|
12213
12225
|
if (append && existsSync2(filePath)) {
|
|
12214
12226
|
appendFileSync(filePath, `${lines.join("\n")}
|
|
@@ -12298,16 +12310,25 @@ var writeJsonlRows = (rows, _fields, notices) => {
|
|
|
12298
12310
|
for (const line of notices) writeLine(line);
|
|
12299
12311
|
for (const row of rows) writeLine(JSON.stringify(row));
|
|
12300
12312
|
};
|
|
12313
|
+
var OMITTED = "(omitted)";
|
|
12314
|
+
var OMITTED_FALSE = `false ${OMITTED}`;
|
|
12315
|
+
var OMITTED_LEGEND = `> ${OMITTED} = Google left this field off the row, which is how it sends false, 0 and "not set": the field WAS read. A field with no column here was not read.`;
|
|
12316
|
+
function mdCell(row, field, booleanColumns) {
|
|
12317
|
+
if (!(field in row)) return booleanColumns.has(field) ? OMITTED_FALSE : OMITTED;
|
|
12318
|
+
return String(row[field] ?? "").replace(/\|/g, "\\|");
|
|
12319
|
+
}
|
|
12301
12320
|
var writeMdRows = (rows, fields, notices) => {
|
|
12321
|
+
const booleanColumns = new Set(fields.filter((f) => rows.some((row) => typeof row[f] === "boolean")));
|
|
12302
12322
|
for (const line of notices) writeLine(line);
|
|
12303
12323
|
writeLine(`| ${fields.join(" | ")} |`);
|
|
12304
12324
|
writeLine(`| ${fields.map(() => "---").join(" | ")} |`);
|
|
12305
|
-
for (const row of rows) writeLine(`| ${fields.map((f) =>
|
|
12325
|
+
for (const row of rows) writeLine(`| ${fields.map((f) => mdCell(row, f, booleanColumns)).join(" | ")} |`);
|
|
12326
|
+
if (rows.some((row) => fields.some((f) => !(f in row)))) writeLine(OMITTED_LEGEND);
|
|
12306
12327
|
};
|
|
12307
12328
|
var ROW_WRITERS = { csv: writeCsvRows, jsonl: writeJsonlRows, md: writeMdRows };
|
|
12308
|
-
function writeRows(format, rows, notices) {
|
|
12329
|
+
function writeRows(format, rows, columns, notices) {
|
|
12309
12330
|
const write2 = ROW_WRITERS[format] ?? writeMdRows;
|
|
12310
|
-
write2(rows,
|
|
12331
|
+
write2(rows, columns, notices);
|
|
12311
12332
|
if (rows.length > REPEAT_NOTICE_ABOVE_ROWS) {
|
|
12312
12333
|
for (const line of notices) writeLine(line);
|
|
12313
12334
|
}
|
|
@@ -12334,7 +12355,7 @@ function outputResults(rows, fieldDescs, warnings, args, cached, read) {
|
|
|
12334
12355
|
);
|
|
12335
12356
|
return;
|
|
12336
12357
|
}
|
|
12337
|
-
writeRows(format, rows, warningLines(allWarnings, format));
|
|
12358
|
+
writeRows(format, rows, resultColumns(read.gaql, rows), warningLines(allWarnings, format));
|
|
12338
12359
|
}
|
|
12339
12360
|
function resolveGaql(args, limit) {
|
|
12340
12361
|
if (args.preset) {
|
|
@@ -12453,15 +12474,27 @@ function missingFieldWarningsFor(gaql, observed, rowCount2) {
|
|
|
12453
12474
|
}
|
|
12454
12475
|
];
|
|
12455
12476
|
}
|
|
12477
|
+
function unwrittenColumnWarnings(header, observed) {
|
|
12478
|
+
const written = new Set(header);
|
|
12479
|
+
const unwritten = [...observed].filter((key) => !written.has(key));
|
|
12480
|
+
if (unwritten.length === 0) return [];
|
|
12481
|
+
return [
|
|
12482
|
+
{
|
|
12483
|
+
code: "UNWRITTEN_COLUMNS",
|
|
12484
|
+
message: `The file has no column for ${unwritten.join(", ")}: the header was fixed on the first page, which carried none of them, so their values from later pages are NOT in the file. Do not read those columns as empty. SELECT these fields by name instead of their parent, or write to a .jsonl file, then re-run.`
|
|
12485
|
+
}
|
|
12486
|
+
];
|
|
12487
|
+
}
|
|
12456
12488
|
function keywordServingOptions(gaql) {
|
|
12457
12489
|
return { statusReasonsSelected: parseSelectedFields(gaql).includes("ad_group_criterion.primary_status_reasons") };
|
|
12458
12490
|
}
|
|
12459
|
-
async function fetchAllPages(body, args, servingOptions) {
|
|
12491
|
+
async function fetchAllPages(gaql, body, args, servingOptions) {
|
|
12460
12492
|
let allRows = [];
|
|
12461
12493
|
let pageToken = args.cursor;
|
|
12462
12494
|
let totalRowsWritten = 0;
|
|
12463
12495
|
let keywordLimits;
|
|
12464
12496
|
const observedFields = /* @__PURE__ */ new Set();
|
|
12497
|
+
let fileColumns;
|
|
12465
12498
|
do {
|
|
12466
12499
|
const requestBody2 = { ...body };
|
|
12467
12500
|
if (pageToken) requestBody2.pageToken = pageToken;
|
|
@@ -12470,7 +12503,8 @@ async function fetchAllPages(body, args, servingOptions) {
|
|
|
12470
12503
|
pageToken = response.pageToken;
|
|
12471
12504
|
if (args.out) {
|
|
12472
12505
|
const shouldAppend = args.append || totalRowsWritten > 0;
|
|
12473
|
-
|
|
12506
|
+
fileColumns ??= resultColumns(gaql, rows);
|
|
12507
|
+
writeRowsToFile(resolve(args.out), rows, fileColumns, shouldAppend);
|
|
12474
12508
|
totalRowsWritten += rows.length;
|
|
12475
12509
|
keywordLimits = collectKeywordLimits(rows, servingOptions, keywordLimits);
|
|
12476
12510
|
foldObservedFields(rows, observedFields);
|
|
@@ -12484,7 +12518,8 @@ async function fetchAllPages(body, args, servingOptions) {
|
|
|
12484
12518
|
lastPageToken: pageToken,
|
|
12485
12519
|
totalRowsWritten,
|
|
12486
12520
|
keywordLimits: keywordLimits ?? collectKeywordLimits([], servingOptions),
|
|
12487
|
-
observedFields
|
|
12521
|
+
observedFields,
|
|
12522
|
+
fileColumns: fileColumns ?? []
|
|
12488
12523
|
};
|
|
12489
12524
|
}
|
|
12490
12525
|
async function executeQuery(finalQuery, args, customerId, limit, warnings, useCache, cacheKey) {
|
|
@@ -12494,7 +12529,8 @@ async function executeQuery(finalQuery, args, customerId, limit, warnings, useCa
|
|
|
12494
12529
|
if (args.cursor) body.pageToken = args.cursor;
|
|
12495
12530
|
if (!useCache) body.skipCache = true;
|
|
12496
12531
|
const servingOptions = keywordServingOptions(finalQuery);
|
|
12497
|
-
const { allRows, lastPageToken, totalRowsWritten, keywordLimits, observedFields } = await fetchAllPages(
|
|
12532
|
+
const { allRows, lastPageToken, totalRowsWritten, keywordLimits, observedFields, fileColumns } = await fetchAllPages(
|
|
12533
|
+
finalQuery,
|
|
12498
12534
|
body,
|
|
12499
12535
|
args,
|
|
12500
12536
|
servingOptions
|
|
@@ -12510,6 +12546,8 @@ async function executeQuery(finalQuery, args, customerId, limit, warnings, useCa
|
|
|
12510
12546
|
...warnings,
|
|
12511
12547
|
...renderKeywordLimits(keywordLimits),
|
|
12512
12548
|
...missingFieldWarningsFor(finalQuery, observedFields, totalRowsWritten),
|
|
12549
|
+
// Only a CSV has a header to fall out of; json and jsonl files carry every row whole.
|
|
12550
|
+
...isCsvPath(args.out) ? unwrittenColumnWarnings(fileColumns, observedFields) : [],
|
|
12513
12551
|
...conversionMixWarnings(finalQuery),
|
|
12514
12552
|
...truncation
|
|
12515
12553
|
];
|
|
@@ -12541,6 +12579,7 @@ async function executeQuery(finalQuery, args, customerId, limit, warnings, useCa
|
|
|
12541
12579
|
args,
|
|
12542
12580
|
false,
|
|
12543
12581
|
{
|
|
12582
|
+
gaql: finalQuery,
|
|
12544
12583
|
effectiveLimit: effectiveRowLimit(finalQuery, limit, Boolean(args.all)),
|
|
12545
12584
|
all: Boolean(args.all),
|
|
12546
12585
|
nextCursor: lastPageToken
|
|
@@ -12658,7 +12697,11 @@ Examples:
|
|
|
12658
12697
|
],
|
|
12659
12698
|
args,
|
|
12660
12699
|
true,
|
|
12661
|
-
{
|
|
12700
|
+
{
|
|
12701
|
+
gaql: finalQuery,
|
|
12702
|
+
effectiveLimit: effectiveRowLimit(finalQuery, limit, Boolean(args.all)),
|
|
12703
|
+
all: Boolean(args.all)
|
|
12704
|
+
}
|
|
12662
12705
|
);
|
|
12663
12706
|
return;
|
|
12664
12707
|
}
|
|
@@ -21723,7 +21766,26 @@ var OPTIMIZATION_GOALS = [
|
|
|
21723
21766
|
"THRUPLAY",
|
|
21724
21767
|
"VALUE",
|
|
21725
21768
|
"CONVERSATIONS",
|
|
21726
|
-
"DERIVED_EVENTS"
|
|
21769
|
+
"DERIVED_EVENTS",
|
|
21770
|
+
// Added 2026-09-18 from Meta's own generated enum spec
|
|
21771
|
+
// (facebook-business-sdk-codegen › AdSet_optimization_goal). Every one of these
|
|
21772
|
+
// is a goal Meta accepts and Baker used to refuse at stage time — the messaging
|
|
21773
|
+
// and Instagram-profile goals are the ones that cost real campaigns, because a
|
|
21774
|
+
// click-to-WhatsApp ad set cannot be expressed without them.
|
|
21775
|
+
"ADVERTISER_SILOED_VALUE",
|
|
21776
|
+
"APP_INSTALLS_AND_OFFSITE_CONVERSIONS",
|
|
21777
|
+
"AUTOMATIC_OBJECTIVE",
|
|
21778
|
+
"ENGAGED_PAGE_VIEWS",
|
|
21779
|
+
"IN_APP_VALUE",
|
|
21780
|
+
"MEANINGFUL_CALL_ATTEMPT",
|
|
21781
|
+
"MESSAGING_APPOINTMENT_CONVERSION",
|
|
21782
|
+
"MESSAGING_DEEP_CONVERSATION_AND_FOLLOW",
|
|
21783
|
+
"MESSAGING_PURCHASE_CONVERSION",
|
|
21784
|
+
"PROFILE_AND_PAGE_ENGAGEMENT",
|
|
21785
|
+
"PROFILE_VISIT",
|
|
21786
|
+
"REMINDERS_SET",
|
|
21787
|
+
"SUBSCRIBERS",
|
|
21788
|
+
"VISIT_INSTAGRAM_PROFILE"
|
|
21727
21789
|
];
|
|
21728
21790
|
var OPTIMIZATION_GOALS_REQUIRING_PAGE = [
|
|
21729
21791
|
"LEAD_GENERATION",
|
|
@@ -21745,7 +21807,23 @@ var DESTINATION_TYPES = [
|
|
|
21745
21807
|
"WHATSAPP",
|
|
21746
21808
|
"PHONE_CALL",
|
|
21747
21809
|
"INSTAGRAM_PROFILE",
|
|
21748
|
-
"SHOP_AUTOMATIC"
|
|
21810
|
+
"SHOP_AUTOMATIC",
|
|
21811
|
+
// Added 2026-09-18 from adcampaign_destination_type. The four MESSAGING_* values
|
|
21812
|
+
// are the click-to-message destinations (Messenger / Instagram Direct / WhatsApp
|
|
21813
|
+
// in every combination Meta ships); without them Baker could not stage a
|
|
21814
|
+
// click-to-WhatsApp ad set at all.
|
|
21815
|
+
"APPLINKS_AUTOMATIC",
|
|
21816
|
+
"FACEBOOK",
|
|
21817
|
+
"FACEBOOK_LIVE",
|
|
21818
|
+
"FACEBOOK_PAGE",
|
|
21819
|
+
"IMAGINE",
|
|
21820
|
+
"INSTAGRAM_LIVE",
|
|
21821
|
+
"INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE",
|
|
21822
|
+
"MESSAGING_INSTAGRAM_DIRECT_MESSENGER",
|
|
21823
|
+
"MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP",
|
|
21824
|
+
"MESSAGING_INSTAGRAM_DIRECT_WHATSAPP",
|
|
21825
|
+
"MESSAGING_MESSENGER_WHATSAPP",
|
|
21826
|
+
"ON_EVENT"
|
|
21749
21827
|
];
|
|
21750
21828
|
var CUSTOM_EVENT_TYPES = [
|
|
21751
21829
|
"PURCHASE",
|
|
@@ -21759,7 +21837,28 @@ var CUSTOM_EVENT_TYPES = [
|
|
|
21759
21837
|
"SUBSCRIBE",
|
|
21760
21838
|
"START_TRIAL",
|
|
21761
21839
|
"CONTACT",
|
|
21762
|
-
"OTHER"
|
|
21840
|
+
"OTHER",
|
|
21841
|
+
// Added 2026-09-18 from AdPromotedObject_custom_event_type. SCHEDULE,
|
|
21842
|
+
// SUBMIT_APPLICATION, SERVICE_BOOKING_REQUEST and
|
|
21843
|
+
// MESSAGING_CONVERSATION_STARTED_7D are the conversions service businesses and
|
|
21844
|
+
// click-to-message campaigns actually optimize toward.
|
|
21845
|
+
"ACHIEVEMENT_UNLOCKED",
|
|
21846
|
+
"ADD_TO_WISHLIST",
|
|
21847
|
+
"AD_IMPRESSION",
|
|
21848
|
+
"CUSTOMIZE_PRODUCT",
|
|
21849
|
+
"D2_RETENTION",
|
|
21850
|
+
"D7_RETENTION",
|
|
21851
|
+
"DONATE",
|
|
21852
|
+
"FIND_LOCATION",
|
|
21853
|
+
"LEVEL_ACHIEVED",
|
|
21854
|
+
"LISTING_INTERACTION",
|
|
21855
|
+
"MESSAGING_CONVERSATION_STARTED_7D",
|
|
21856
|
+
"RATE",
|
|
21857
|
+
"SCHEDULE",
|
|
21858
|
+
"SERVICE_BOOKING_REQUEST",
|
|
21859
|
+
"SPENT_CREDITS",
|
|
21860
|
+
"SUBMIT_APPLICATION",
|
|
21861
|
+
"TUTORIAL_COMPLETION"
|
|
21763
21862
|
];
|
|
21764
21863
|
var ATTRIBUTION_EVENT_TYPES = ["CLICK_THROUGH", "VIEW_THROUGH", "ENGAGED_VIDEO_VIEW"];
|
|
21765
21864
|
var ADVANTAGE_AUDIENCE_OPEN_AGE_MAX = 65;
|
|
@@ -21878,7 +21977,37 @@ var CTA_TYPES2 = [
|
|
|
21878
21977
|
"LINK_CARD",
|
|
21879
21978
|
"DIAL_CODE",
|
|
21880
21979
|
"FIND_YOUR_GROUPS",
|
|
21881
|
-
"START_ORDER"
|
|
21980
|
+
"START_ORDER",
|
|
21981
|
+
// Added 2026-09-18 from Meta's generated enum spec
|
|
21982
|
+
// (AdCreative_call_to_action_type). Additive on purpose: 37 of the values above
|
|
21983
|
+
// are absent from that spec and present in the live 400, which is the
|
|
21984
|
+
// disagreement the note above is about — so the spec widens this list and never
|
|
21985
|
+
// prunes it.
|
|
21986
|
+
"AUDIO_CALL",
|
|
21987
|
+
"BROWSE_SHOP",
|
|
21988
|
+
"CONTACT",
|
|
21989
|
+
"FIND_A_GROUP",
|
|
21990
|
+
"FIND_OUT_MORE",
|
|
21991
|
+
"FOLLOW_NEWS_STORYLINE",
|
|
21992
|
+
"FOLLOW_PAGE",
|
|
21993
|
+
"FOLLOW_USER",
|
|
21994
|
+
"GET_DETAILS",
|
|
21995
|
+
"GET_STARTED",
|
|
21996
|
+
"SAY_THANKS",
|
|
21997
|
+
"SEE_SHOP",
|
|
21998
|
+
"SEND_A_GIFT",
|
|
21999
|
+
"SEND_GIFT_MONEY",
|
|
22000
|
+
"SHARE",
|
|
22001
|
+
"SHOP_WITH_AI",
|
|
22002
|
+
"SOTTO_SUBSCRIBE",
|
|
22003
|
+
"TRY_ON_WITH_AI",
|
|
22004
|
+
"VIDEO_ANNOTATION",
|
|
22005
|
+
"VIDEO_CALL",
|
|
22006
|
+
"VIEW_CART",
|
|
22007
|
+
"VIEW_IN_CART",
|
|
22008
|
+
"VISIT_PAGES_FEED",
|
|
22009
|
+
"VISIT_WEBSITE",
|
|
22010
|
+
"WOODHENGE_SUPPORT"
|
|
21882
22011
|
];
|
|
21883
22012
|
var ASSET_FEED_CTA_TYPES = [
|
|
21884
22013
|
"OPEN_LINK",
|
|
@@ -21997,6 +22126,11 @@ var ENROLL_STATUSES = ["OPT_IN", "OPT_OUT"];
|
|
|
21997
22126
|
var STAGEABLE_CREATE_STATUSES4 = ["ACTIVE", "PAUSED"];
|
|
21998
22127
|
var UPDATE_STATUSES = ["ACTIVE", "PAUSED", "ARCHIVED"];
|
|
21999
22128
|
var CUSTOM_AUDIENCE_SUBTYPES = ["CUSTOM", "WEBSITE", "ENGAGEMENT", "LOOKALIKE"];
|
|
22129
|
+
var CUSTOMER_FILE_SOURCES = [
|
|
22130
|
+
"USER_PROVIDED_ONLY",
|
|
22131
|
+
"PARTNER_PROVIDED_ONLY",
|
|
22132
|
+
"BOTH_USER_AND_PARTNER_PROVIDED"
|
|
22133
|
+
];
|
|
22000
22134
|
var MEDIA_KINDS = ["image", "video"];
|
|
22001
22135
|
var CURRENCY_MINIMUMS2 = {
|
|
22002
22136
|
USD: { dailyBudgetMin: 1 },
|
|
@@ -22014,6 +22148,119 @@ import { z as z30 } from "zod";
|
|
|
22014
22148
|
// ../api/src/ads-meta/placements.ts
|
|
22015
22149
|
var FACEBOOK_POSITIONS_REQUIRING_FEED = ["marketplace", "search", "profile_feed", "notification"];
|
|
22016
22150
|
var PLACEMENT_TARGETING_DOC = "https://developers.facebook.com/docs/marketing-api/audiences/reference/placement-targeting/";
|
|
22151
|
+
var PUBLISHER_PLATFORMS = ["facebook", "instagram", "threads", "messenger", "audience_network"];
|
|
22152
|
+
var FACEBOOK_POSITIONS = [
|
|
22153
|
+
"feed",
|
|
22154
|
+
"right_hand_column",
|
|
22155
|
+
"marketplace",
|
|
22156
|
+
"video_feeds",
|
|
22157
|
+
"story",
|
|
22158
|
+
"search",
|
|
22159
|
+
"instream_video",
|
|
22160
|
+
"facebook_reels",
|
|
22161
|
+
"facebook_reels_overlay",
|
|
22162
|
+
"profile_feed",
|
|
22163
|
+
"notification"
|
|
22164
|
+
];
|
|
22165
|
+
var INSTAGRAM_POSITIONS = [
|
|
22166
|
+
"stream",
|
|
22167
|
+
"story",
|
|
22168
|
+
"explore",
|
|
22169
|
+
"explore_home",
|
|
22170
|
+
"reels",
|
|
22171
|
+
"profile_feed",
|
|
22172
|
+
"ig_search",
|
|
22173
|
+
"profile_reels"
|
|
22174
|
+
];
|
|
22175
|
+
var AUDIENCE_NETWORK_POSITIONS = ["classic", "rewarded_video"];
|
|
22176
|
+
var MESSENGER_POSITIONS = ["sponsored_messages", "story"];
|
|
22177
|
+
var THREADS_POSITIONS = ["threads_stream"];
|
|
22178
|
+
var DEVICE_PLATFORMS = ["mobile", "desktop", "connected_tv"];
|
|
22179
|
+
var PLACEMENT_VOCABULARIES = {
|
|
22180
|
+
publisher_platforms: PUBLISHER_PLATFORMS,
|
|
22181
|
+
facebook_positions: FACEBOOK_POSITIONS,
|
|
22182
|
+
instagram_positions: INSTAGRAM_POSITIONS,
|
|
22183
|
+
audience_network_positions: AUDIENCE_NETWORK_POSITIONS,
|
|
22184
|
+
messenger_positions: MESSENGER_POSITIONS,
|
|
22185
|
+
threads_positions: THREADS_POSITIONS,
|
|
22186
|
+
device_platforms: DEVICE_PLATFORMS
|
|
22187
|
+
};
|
|
22188
|
+
var SAME_SURFACE_ON_THIS_PLATFORM = {
|
|
22189
|
+
facebook_positions: { reels: "facebook_reels", stream: "feed", stories: "story", ig_search: "search" },
|
|
22190
|
+
instagram_positions: {
|
|
22191
|
+
feed: "stream",
|
|
22192
|
+
facebook_reels: "reels",
|
|
22193
|
+
stories: "story",
|
|
22194
|
+
search: "ig_search"
|
|
22195
|
+
}
|
|
22196
|
+
};
|
|
22197
|
+
function placementFieldsAccepting(value, except) {
|
|
22198
|
+
return Object.keys(PLACEMENT_VOCABULARIES).filter(
|
|
22199
|
+
(field) => field !== except && PLACEMENT_VOCABULARIES[field].includes(value)
|
|
22200
|
+
);
|
|
22201
|
+
}
|
|
22202
|
+
function placementValueIssue(field, value) {
|
|
22203
|
+
const renamed = SAME_SURFACE_ON_THIS_PLATFORM[field]?.[value];
|
|
22204
|
+
if (renamed) {
|
|
22205
|
+
return {
|
|
22206
|
+
field,
|
|
22207
|
+
value,
|
|
22208
|
+
kind: "wrong-field",
|
|
22209
|
+
fix: `Meta calls that surface "${renamed}" in ${field} \u2014 use "${renamed}"`
|
|
22210
|
+
};
|
|
22211
|
+
}
|
|
22212
|
+
const elsewhere = placementFieldsAccepting(value, field);
|
|
22213
|
+
if (elsewhere.length > 0) {
|
|
22214
|
+
return {
|
|
22215
|
+
field,
|
|
22216
|
+
value,
|
|
22217
|
+
kind: "wrong-field",
|
|
22218
|
+
fix: `"${value}" belongs to ${elsewhere.join(" / ")}, not ${field} \u2014 move it there or drop it`
|
|
22219
|
+
};
|
|
22220
|
+
}
|
|
22221
|
+
return {
|
|
22222
|
+
field,
|
|
22223
|
+
value,
|
|
22224
|
+
kind: "unknown",
|
|
22225
|
+
fix: `"${value}" is not a placement Baker knows; ${field} accepts ${PLACEMENT_VOCABULARIES[field].join(", ")}`
|
|
22226
|
+
};
|
|
22227
|
+
}
|
|
22228
|
+
function invalidPlacementValues(targeting) {
|
|
22229
|
+
const issues = [];
|
|
22230
|
+
for (const field of Object.keys(PLACEMENT_VOCABULARIES)) {
|
|
22231
|
+
const allowed = PLACEMENT_VOCABULARIES[field];
|
|
22232
|
+
const selected = targeting?.[field] ?? [];
|
|
22233
|
+
for (const value of selected) {
|
|
22234
|
+
if (!allowed.includes(value)) {
|
|
22235
|
+
issues.push(placementValueIssue(field, value));
|
|
22236
|
+
}
|
|
22237
|
+
}
|
|
22238
|
+
}
|
|
22239
|
+
return issues;
|
|
22240
|
+
}
|
|
22241
|
+
function placementCoRequirementIssues(targeting) {
|
|
22242
|
+
const facebook = targeting?.facebook_positions ?? [];
|
|
22243
|
+
const instagram = targeting?.instagram_positions ?? [];
|
|
22244
|
+
const threads = targeting?.threads_positions ?? [];
|
|
22245
|
+
const devices = targeting?.device_platforms;
|
|
22246
|
+
const issues = [];
|
|
22247
|
+
if (facebook.includes("story")) {
|
|
22248
|
+
if (!facebook.includes("feed") && !instagram.includes("story")) {
|
|
22249
|
+
issues.push(
|
|
22250
|
+
'Meta refuses Facebook "story" on its own \u2014 select "feed" in facebook_positions or "story" in instagram_positions alongside it'
|
|
22251
|
+
);
|
|
22252
|
+
}
|
|
22253
|
+
if (devices && !devices.includes("mobile")) {
|
|
22254
|
+
issues.push(
|
|
22255
|
+
'Facebook "story" only runs on mobile \u2014 add "mobile" to device_platforms or drop the story placement'
|
|
22256
|
+
);
|
|
22257
|
+
}
|
|
22258
|
+
}
|
|
22259
|
+
if (threads.includes("threads_stream") && !instagram.includes("stream")) {
|
|
22260
|
+
issues.push('Threads "threads_stream" cannot be selected alone \u2014 add "stream" to instagram_positions');
|
|
22261
|
+
}
|
|
22262
|
+
return issues;
|
|
22263
|
+
}
|
|
22017
22264
|
var CONSTRAINED_PLACEMENTS = [
|
|
22018
22265
|
{
|
|
22019
22266
|
position: "facebook_reels_overlay",
|
|
@@ -22128,6 +22375,7 @@ var metaTargetingSchema = z30.object({
|
|
|
22128
22375
|
instagram_positions: z30.array(z30.string()).optional(),
|
|
22129
22376
|
audience_network_positions: z30.array(z30.string()).optional(),
|
|
22130
22377
|
messenger_positions: z30.array(z30.string()).optional(),
|
|
22378
|
+
threads_positions: z30.array(z30.string()).optional(),
|
|
22131
22379
|
device_platforms: z30.array(z30.string()).optional(),
|
|
22132
22380
|
targeting_automation: z30.object({ advantage_audience: z30.union([z30.literal(0), z30.literal(1)]) }).partial().optional()
|
|
22133
22381
|
}).catchall(z30.unknown());
|
|
@@ -22259,6 +22507,13 @@ var adSetFields = {
|
|
|
22259
22507
|
start_time: z30.number().int().positive().optional(),
|
|
22260
22508
|
end_time: z30.number().int().positive().optional(),
|
|
22261
22509
|
targeting: metaTargetingSchema,
|
|
22510
|
+
/**
|
|
22511
|
+
* The live ad set this one was copied from, verbatim, by `duplicate`. Baker
|
|
22512
|
+
* only — `adSetCommonBody` never sends it to Meta. Set exclusively when the
|
|
22513
|
+
* targeting IS the source's targeting, because that is what it licenses:
|
|
22514
|
+
* placement validation is skipped for it (see `validateAdSetPlacements`).
|
|
22515
|
+
*/
|
|
22516
|
+
clonedFrom: z30.string().regex(NUMERIC_ID_REGEX3).optional(),
|
|
22262
22517
|
/** EU Digital Services Act: who benefits from / pays for the ad. Auto-filled from the account for EU geo when omitted. */
|
|
22263
22518
|
dsa_beneficiary: z30.string().min(1).max(100).optional(),
|
|
22264
22519
|
dsa_payor: z30.string().min(1).max(100).optional()
|
|
@@ -22359,6 +22614,9 @@ function validateAdvantageAudience(p, ctx) {
|
|
|
22359
22614
|
}
|
|
22360
22615
|
}
|
|
22361
22616
|
function validateAdSetPlacements(p, ctx) {
|
|
22617
|
+
if (p.clonedFrom) {
|
|
22618
|
+
return;
|
|
22619
|
+
}
|
|
22362
22620
|
const missingFeed = positionsMissingRequiredFeed(p.targeting);
|
|
22363
22621
|
if (missingFeed.length > 0) {
|
|
22364
22622
|
ctx.addIssue({
|
|
@@ -22367,6 +22625,19 @@ function validateAdSetPlacements(p, ctx) {
|
|
|
22367
22625
|
message: `Meta requires "feed" alongside ${missingFeed.join(", ")} \u2014 add "feed" to facebook_positions or drop those positions (${PLACEMENT_TARGETING_DOC})`
|
|
22368
22626
|
});
|
|
22369
22627
|
}
|
|
22628
|
+
for (const issue of invalidPlacementValues(p.targeting)) {
|
|
22629
|
+
if (issue.kind !== "wrong-field") {
|
|
22630
|
+
continue;
|
|
22631
|
+
}
|
|
22632
|
+
ctx.addIssue({
|
|
22633
|
+
code: "custom",
|
|
22634
|
+
path: ["targeting", issue.field],
|
|
22635
|
+
message: `${issue.field} does not accept "${issue.value}". ${issue.fix} (${PLACEMENT_TARGETING_DOC})`
|
|
22636
|
+
});
|
|
22637
|
+
}
|
|
22638
|
+
for (const message of placementCoRequirementIssues(p.targeting)) {
|
|
22639
|
+
ctx.addIssue({ code: "custom", path: ["targeting"], message: `${message} (${PLACEMENT_TARGETING_DOC})` });
|
|
22640
|
+
}
|
|
22370
22641
|
}
|
|
22371
22642
|
var adSetCreateSchema = z30.object(adSetFields).superRefine((p, ctx) => {
|
|
22372
22643
|
validateAdSetBudgetAndBid(p, ctx);
|
|
@@ -22683,7 +22954,7 @@ var customAudienceCreateSchema = z30.object({
|
|
|
22683
22954
|
name: z30.string().min(1).max(META_LIMITS.audience.nameMax),
|
|
22684
22955
|
subtype: z30.enum(CUSTOM_AUDIENCE_SUBTYPES),
|
|
22685
22956
|
description: z30.string().max(500).optional(),
|
|
22686
|
-
customer_file_source: z30.
|
|
22957
|
+
customer_file_source: z30.enum(CUSTOMER_FILE_SOURCES).optional(),
|
|
22687
22958
|
retention_days: z30.number().int().min(1).max(META_LIMITS.audience.retentionDaysMax).optional(),
|
|
22688
22959
|
lookalike_spec: lookalikeSpecSchema.optional(),
|
|
22689
22960
|
/** Website/engagement rule — structural passthrough validated by Meta. */
|
|
@@ -57359,7 +57630,7 @@ registerSchema({
|
|
|
57359
57630
|
},
|
|
57360
57631
|
sort: {
|
|
57361
57632
|
type: "string",
|
|
57362
|
-
description: "Sort:
|
|
57633
|
+
description: "Sort: search_volume, competition, cpc (default: search_volume)",
|
|
57363
57634
|
required: false
|
|
57364
57635
|
},
|
|
57365
57636
|
type: { type: "string", description: "Filter: paid, organic, all (default: all)", required: false },
|
|
@@ -57388,7 +57659,7 @@ Examples:
|
|
|
57388
57659
|
target: { type: "positional", description: "Domain or URL to analyze", required: true },
|
|
57389
57660
|
location: { type: "string", description: "Country code or location code (default: us)", required: false },
|
|
57390
57661
|
language: { type: "string", description: "Language code or name (default: en)", required: false },
|
|
57391
|
-
sort: { type: "string", description: "Sort:
|
|
57662
|
+
sort: { type: "string", description: "Sort: search_volume, competition, cpc", required: false },
|
|
57392
57663
|
type: { type: "string", description: "Filter: paid, organic, all (default: all)", required: false },
|
|
57393
57664
|
limit: { type: "string", description: "Max results (default: 50, max: 1000)", required: false },
|
|
57394
57665
|
output: { type: "string", description: "Format: json|csv|md|jsonl", required: false, default: "json" },
|