@koda-sl/baker-cli 0.333.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(),
@@ -4918,7 +4989,15 @@ var scheduledEffectSchema = z13.object({
4918
4989
  op: effectOpSchema,
4919
4990
  staged: z13.boolean(),
4920
4991
  name: z13.string(),
4921
- summary: z13.string().nullable()
4992
+ summary: z13.string().nullable(),
4993
+ /**
4994
+ * What the publish did with this op, or null while it is still staged. `op`
4995
+ * is what was asked for; this is whether it happened — a task whose schedule
4996
+ * could not be saved still reads `op: "created"`.
4997
+ */
4998
+ outcome: z13.enum(["applied", "failed", "skipped"]).nullable(),
4999
+ /** Why it failed or was skipped, or how it was applied differently (e.g. switched off). */
5000
+ reason: z13.string().nullable()
4922
5001
  });
4923
5002
  var tagEffectSchema = z13.object({
4924
5003
  op: effectOpSchema,
@@ -19929,7 +20008,11 @@ registerSchema({
19929
20008
  name: { type: "string", description: "Audience name (\u2264100 chars)", required: true },
19930
20009
  type: { type: "string", description: "company-list|user-list|retargeting|engagement", required: true },
19931
20010
  "list-file": { type: "string", description: "CSV file with the list rows", required: false },
19932
- 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
+ }
19933
20016
  }
19934
20017
  });
19935
20018
  registerSchema({
@@ -20681,7 +20764,10 @@ Example: baker ads linkedin audiences create --name "ABM Tier-1" --type company-
20681
20764
  name: { type: "string", description: "Audience name (\u2264100 chars)" },
20682
20765
  type: { type: "string", description: AUDIENCE_TYPES2.join("|") },
20683
20766
  "list-file": { type: "string", description: "CSV file with the list rows (header row required)" },
20684
- 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
+ }
20685
20771
  },
20686
20772
  run: async ({ args: args5 }) => {
20687
20773
  const accountId = bareAccountId(args5);
@@ -22365,6 +22451,10 @@ function renderOutcome2(data, lines) {
22365
22451
  lines.push(`LinkedIn Ads \u2014 published \xB7 ${summary || `${data.count} op(s)`} \xB7 ${data.mode}`);
22366
22452
  for (const op of data.ops ?? []) {
22367
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
+ }
22368
22458
  if (status !== "failed" && status !== "skipped") {
22369
22459
  continue;
22370
22460
  }