@koda-sl/baker-cli 0.334.0 → 0.335.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
@@ -1887,7 +1887,78 @@ var audienceCreateSchema = z3.object({
1887
1887
  message: `${p.audienceType} audiences need a raw source config (use --file)`
1888
1888
  });
1889
1889
  }
1890
- });
1890
+ if (!isList && p.raw) {
1891
+ for (const message of retargetingConfigProblems(p.raw)) {
1892
+ ctx.addIssue({ code: "custom", path: ["raw"], message: `${p.audienceType} source config: ${message}` });
1893
+ }
1894
+ }
1895
+ });
1896
+ var ENGAGEMENT_LOOKBACK_DAYS = [7, 14, 28, 30, 60, 90, 180, 365];
1897
+ var WEBSITE_LOOKBACK_DAYS = ENGAGEMENT_LOOKBACK_DAYS.filter((days) => days >= 14 && days <= 180);
1898
+ var EVENT_LOOKBACK_DAYS = [730];
1899
+ var WEBSITE_SOURCE_PLATFORM = "LINKEDIN_WEBSITE_RETARGETING";
1900
+ var TRIGGER_SOURCE_TYPE_BY_PLATFORM = { [WEBSITE_SOURCE_PLATFORM]: "WEBSITE" };
1901
+ function lookbackDaysFor(sourcePlatform) {
1902
+ if (sourcePlatform === WEBSITE_SOURCE_PLATFORM) {
1903
+ return WEBSITE_LOOKBACK_DAYS;
1904
+ }
1905
+ return sourcePlatform === "EVENT_PAGES" ? EVENT_LOOKBACK_DAYS : ENGAGEMENT_LOOKBACK_DAYS;
1906
+ }
1907
+ var ENGAGEMENT_TRIGGER_URN = /^urn:li:dmpEngagementTrigger:\(urn:li:dmpEngagementSourceType:([A-Z_]+),([A-Z_]+)\)$/;
1908
+ var RETARGETING_EXAMPLE = '{"sourcePlatform":"VIDEO_ADS","type":"USER","engagementRules":[{"engagementSource":"urn:li:sponsoredCampaign:123","engagementTrigger":"urn:li:dmpEngagementTrigger:(urn:li:dmpEngagementSourceType:VIDEO_ADS,FIRST_QUARTILE)","timeFrame":{"timeSpan":{"duration":90,"unit":"DAY"}}}]}';
1909
+ var engagementRuleSchema = z3.object({
1910
+ engagementSource: z3.string().regex(/^urn:li:[A-Za-z]+:\S+$/, "engagementSource must be a URN (urn:li:\u2026)"),
1911
+ engagementTrigger: z3.string().regex(
1912
+ ENGAGEMENT_TRIGGER_URN,
1913
+ "engagementTrigger must be urn:li:dmpEngagementTrigger:(urn:li:dmpEngagementSourceType:<SOURCE>,<TRIGGER>)"
1914
+ ),
1915
+ timeFrame: z3.object({
1916
+ timeSpan: z3.object({ duration: z3.number().int().positive(), unit: z3.literal("DAY") })
1917
+ })
1918
+ }).passthrough();
1919
+ var engagementRulesSchema = z3.array(engagementRuleSchema).min(1);
1920
+ function retargetingConfigProblems(raw) {
1921
+ const problems = [];
1922
+ const sourcePlatform = typeof raw.sourcePlatform === "string" ? raw.sourcePlatform.trim() : "";
1923
+ if (!sourcePlatform) {
1924
+ problems.push("missing sourcePlatform");
1925
+ }
1926
+ if (raw.type !== "USER") {
1927
+ problems.push('type must be "USER" \u2014 LinkedIn only attaches engagement rules to USER segments');
1928
+ }
1929
+ const rules = engagementRulesSchema.safeParse(raw.engagementRules);
1930
+ if (!rules.success) {
1931
+ const detail = rules.error.issues.map((issue) => issue.message).join("; ");
1932
+ problems.push(
1933
+ `engagementRules needs at least one source rule (engagementSource, engagementTrigger, timeFrame) \u2014 ${detail}. e.g. ${RETARGETING_EXAMPLE}`
1934
+ );
1935
+ return problems;
1936
+ }
1937
+ const allowed = lookbackDaysFor(sourcePlatform);
1938
+ const expectedTriggerSource = TRIGGER_SOURCE_TYPE_BY_PLATFORM[sourcePlatform] ?? sourcePlatform;
1939
+ for (const rule of rules.data) {
1940
+ const triggerSource = ENGAGEMENT_TRIGGER_URN.exec(rule.engagementTrigger)?.[1];
1941
+ if (sourcePlatform && triggerSource !== expectedTriggerSource) {
1942
+ problems.push(
1943
+ `engagementTrigger ${rule.engagementTrigger} is a ${triggerSource} trigger, not ${expectedTriggerSource}`
1944
+ );
1945
+ }
1946
+ const days = rule.timeFrame.timeSpan.duration;
1947
+ if (!allowed.includes(days)) {
1948
+ problems.push(`lookback ${days} days is not offered for ${sourcePlatform} \u2014 use ${allowed.join(", ")}`);
1949
+ }
1950
+ }
1951
+ const shapes = new Set(rules.data.map((rule) => `${rule.engagementTrigger}|${rule.timeFrame.timeSpan.duration}`));
1952
+ if (shapes.size > 1) {
1953
+ problems.push("every engagement rule on one audience must use the same trigger and lookback");
1954
+ }
1955
+ const sources = rules.data.map((rule) => rule.engagementSource);
1956
+ const repeated = [...new Set(sources.filter((source, index) => sources.indexOf(source) !== index))];
1957
+ if (repeated.length > 0) {
1958
+ problems.push(`each engagementSource can appear in only one rule: ${repeated.join(", ")}`);
1959
+ }
1960
+ return problems;
1961
+ }
1891
1962
  var audienceUploadListSchema = z3.object({
1892
1963
  segment: parentRefSchema,
1893
1964
  listColumns: z3.array(z3.string().min(1)).optional(),
@@ -19937,7 +20008,11 @@ registerSchema({
19937
20008
  name: { type: "string", description: "Audience name (\u2264100 chars)", required: true },
19938
20009
  type: { type: "string", description: "company-list|user-list|retargeting|engagement", required: true },
19939
20010
  "list-file": { type: "string", description: "CSV file with the list rows", required: false },
19940
- file: { type: "string", description: "JSON raw source config for retargeting/engagement", required: false }
20011
+ file: {
20012
+ type: "string",
20013
+ description: "JSON raw source config for retargeting/engagement \u2014 sourcePlatform, type USER, and engagementRules (engagementSource, engagementTrigger, timeFrame)",
20014
+ required: false
20015
+ }
19941
20016
  }
19942
20017
  });
19943
20018
  registerSchema({
@@ -20689,7 +20764,10 @@ Example: baker ads linkedin audiences create --name "ABM Tier-1" --type company-
20689
20764
  name: { type: "string", description: "Audience name (\u2264100 chars)" },
20690
20765
  type: { type: "string", description: AUDIENCE_TYPES2.join("|") },
20691
20766
  "list-file": { type: "string", description: "CSV file with the list rows (header row required)" },
20692
- file: { type: "string", description: "JSON file with a raw source config (retargeting/engagement)" }
20767
+ file: {
20768
+ type: "string",
20769
+ description: 'JSON file with a raw source config (retargeting/engagement) \u2014 sourcePlatform, type USER and at least one engagementRules entry, e.g. {"sourcePlatform":"VIDEO_ADS","type":"USER","engagementRules":[{"engagementSource":"urn:li:sponsoredCampaign:123","engagementTrigger":"urn:li:dmpEngagementTrigger:(urn:li:dmpEngagementSourceType:VIDEO_ADS,FIRST_QUARTILE)","timeFrame":{"timeSpan":{"duration":90,"unit":"DAY"}}}]}'
20770
+ }
20693
20771
  },
20694
20772
  run: async ({ args: args5 }) => {
20695
20773
  const accountId = bareAccountId(args5);
@@ -22373,6 +22451,10 @@ function renderOutcome2(data, lines) {
22373
22451
  lines.push(`LinkedIn Ads \u2014 published \xB7 ${summary || `${data.count} op(s)`} \xB7 ${data.mode}`);
22374
22452
  for (const op of data.ops ?? []) {
22375
22453
  const status = op.result?.status;
22454
+ if (status === "applied" && op.result?.error) {
22455
+ lines.push(` \u26A0 ${op.summary} \u2014 applied in part: ${op.result.error}`);
22456
+ continue;
22457
+ }
22376
22458
  if (status !== "failed" && status !== "skipped") {
22377
22459
  continue;
22378
22460
  }