@koda-sl/baker-cli 0.149.0 → 0.150.0-dev.04484bc24
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 +8 -0
- package/dist/{chunk-OO5BDH3J.js → chunk-JBDSJZBZ.js} +4 -4
- package/dist/chunk-JBDSJZBZ.js.map +1 -0
- package/dist/cli.js +158 -3
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OO5BDH3J.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
toModelSafeImage,
|
|
37
37
|
ulid,
|
|
38
38
|
validateCanvasDeep
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-JBDSJZBZ.js";
|
|
40
40
|
import {
|
|
41
41
|
csvOrJson,
|
|
42
42
|
daysAgoIso,
|
|
@@ -1297,6 +1297,15 @@ var LINKEDIN_LIMITS = {
|
|
|
1297
1297
|
campaign: {
|
|
1298
1298
|
nameMax: 255
|
|
1299
1299
|
},
|
|
1300
|
+
/**
|
|
1301
|
+
* LinkedIn documents no ceiling on URL tracking parameters; these are our own
|
|
1302
|
+
* guards against a payload that would build an unusable landing URL.
|
|
1303
|
+
*/
|
|
1304
|
+
trackingParams: {
|
|
1305
|
+
keyMax: 100,
|
|
1306
|
+
valueMax: 500,
|
|
1307
|
+
parametersMax: 20
|
|
1308
|
+
},
|
|
1300
1309
|
creative: {
|
|
1301
1310
|
commentarySoftMax: 600,
|
|
1302
1311
|
// feed truncates with "…see more" beyond this
|
|
@@ -1435,6 +1444,17 @@ var CONVERSION_TYPES = [
|
|
|
1435
1444
|
];
|
|
1436
1445
|
var CONVERSION_METHODS = ["INSIGHT_TAG", "CONVERSIONS_API"];
|
|
1437
1446
|
var ATTRIBUTION_TYPES = ["LAST_TOUCH_BY_CAMPAIGN", "LAST_TOUCH_BY_CONVERSION"];
|
|
1447
|
+
var TRACKING_PARAM_DYNAMIC_VALUES = [
|
|
1448
|
+
"ACCOUNT_ID",
|
|
1449
|
+
"ACCOUNT_NAME",
|
|
1450
|
+
"CAMPAIGN_GROUP_ID",
|
|
1451
|
+
"CAMPAIGN_GROUP_NAME",
|
|
1452
|
+
"CAMPAIGN_ID",
|
|
1453
|
+
"CAMPAIGN_NAME",
|
|
1454
|
+
"CREATIVE_ID",
|
|
1455
|
+
"CREATIVE_NAME"
|
|
1456
|
+
];
|
|
1457
|
+
var TRACKING_PARAM_KEY_REGEX = /^[A-Za-z0-9_.-]+$/;
|
|
1438
1458
|
var CURRENCY_MINIMUMS = {
|
|
1439
1459
|
USD: { dailyBudgetMin: 10, unitCostMin: 2 },
|
|
1440
1460
|
EUR: { dailyBudgetMin: 10, unitCostMin: 2 },
|
|
@@ -1592,6 +1612,35 @@ var campaignUpdateSchema = z3.object({
|
|
|
1592
1612
|
}
|
|
1593
1613
|
validateCampaignBudgets(p, ctx, { requireBudget: false });
|
|
1594
1614
|
});
|
|
1615
|
+
var trackingParamKeySchema = z3.string().min(1).max(LINKEDIN_LIMITS.trackingParams.keyMax).regex(TRACKING_PARAM_KEY_REGEX, "tracking parameter keys may only use letters, digits, _ . and -");
|
|
1616
|
+
var trackingParamsSetSchema = z3.object({
|
|
1617
|
+
dynamicValueParameters: z3.record(trackingParamKeySchema, z3.enum(TRACKING_PARAM_DYNAMIC_VALUES)).optional(),
|
|
1618
|
+
customValueParameters: z3.record(trackingParamKeySchema, z3.string().min(1).max(LINKEDIN_LIMITS.trackingParams.valueMax)).optional()
|
|
1619
|
+
}).superRefine((p, ctx) => {
|
|
1620
|
+
if (!p.dynamicValueParameters && !p.customValueParameters) {
|
|
1621
|
+
ctx.addIssue({
|
|
1622
|
+
code: "custom",
|
|
1623
|
+
message: "set at least one parameter \u2014 pass both maps as {} to clear the ad set's tracking parameters instead"
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
const both = Object.keys(p.dynamicValueParameters ?? {}).filter(
|
|
1627
|
+
(key) => p.customValueParameters?.[key] !== void 0
|
|
1628
|
+
);
|
|
1629
|
+
if (both.length > 0) {
|
|
1630
|
+
ctx.addIssue({
|
|
1631
|
+
code: "custom",
|
|
1632
|
+
path: ["customValueParameters"],
|
|
1633
|
+
message: `${both.join(", ")} is set as both a dynamic and a fixed parameter \u2014 LinkedIn appends each key once, so keep only one`
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
const count = Object.keys(p.dynamicValueParameters ?? {}).length + Object.keys(p.customValueParameters ?? {}).length;
|
|
1637
|
+
if (count > LINKEDIN_LIMITS.trackingParams.parametersMax) {
|
|
1638
|
+
ctx.addIssue({
|
|
1639
|
+
code: "custom",
|
|
1640
|
+
message: `${count} parameters \u2014 keep it under ${LINKEDIN_LIMITS.trackingParams.parametersMax}`
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1595
1644
|
var commentarySchema = z3.string().min(1).max(LINKEDIN_LIMITS.creative.commentaryHardMax);
|
|
1596
1645
|
var headlineSchema = z3.string().min(1).max(LINKEDIN_LIMITS.creative.headlineMax);
|
|
1597
1646
|
var httpsUrlSchema = z3.string().url().refine((u) => u.startsWith("https://"), "landing pages must be https");
|
|
@@ -1970,6 +2019,7 @@ var LINKEDIN_DRAFT_OP_KINDS = [
|
|
|
1970
2019
|
"campaignGroup.update",
|
|
1971
2020
|
"campaign.create",
|
|
1972
2021
|
"campaign.update",
|
|
2022
|
+
"trackingParams.set",
|
|
1973
2023
|
"creative.create",
|
|
1974
2024
|
"creative.update",
|
|
1975
2025
|
"audience.create",
|
|
@@ -1993,6 +2043,7 @@ var linkedinDraftOpInputSchema = z3.discriminatedUnion("kind", [
|
|
|
1993
2043
|
updateOp("campaignGroup.update", campaignGroupUpdateSchema),
|
|
1994
2044
|
createOp("campaign.create", campaignCreateSchema),
|
|
1995
2045
|
updateOp("campaign.update", campaignUpdateSchema),
|
|
2046
|
+
updateOp("trackingParams.set", trackingParamsSetSchema),
|
|
1996
2047
|
createOp("creative.create", creativeCreateSchema),
|
|
1997
2048
|
updateOp("creative.update", creativeUpdateSchema),
|
|
1998
2049
|
createOp("audience.create", audienceCreateSchema),
|
|
@@ -8305,6 +8356,26 @@ registerSchema({
|
|
|
8305
8356
|
file: { type: "string", description: "JSON file with fields to change", required: false }
|
|
8306
8357
|
}
|
|
8307
8358
|
});
|
|
8359
|
+
registerSchema({
|
|
8360
|
+
command: "ads.linkedin.campaigns.url-params",
|
|
8361
|
+
description: "Start here for UTMs: stage the URL tracking parameters on an ad set. LinkedIn appends them to the link of EVERY ad in the ad set, including ads already running, so this is the level to set campaign-wide UTMs at \u2014 editing each ad's landing URL instead risks the same key landing twice. --param takes fixed values (repeatable or &-joined); --dynamic takes a value LinkedIn fills in per ad (ACCOUNT_ID, ACCOUNT_NAME, CAMPAIGN_GROUP_ID, CAMPAIGN_GROUP_NAME, CAMPAIGN_ID, CAMPAIGN_NAME, CREATIVE_ID, CREATIVE_NAME). Applies on chat publish; message and conversation ads are unaffected.",
|
|
8362
|
+
args: {
|
|
8363
|
+
id: { type: "positional", description: "Ad set (campaign) id or URN", required: true },
|
|
8364
|
+
...writeAccountArgs,
|
|
8365
|
+
param: {
|
|
8366
|
+
type: "string",
|
|
8367
|
+
description: 'Fixed key=value, repeatable or &-joined \u2014 e.g. "utm_source=linkedin&utm_medium=paid-social"',
|
|
8368
|
+
required: false
|
|
8369
|
+
},
|
|
8370
|
+
dynamic: {
|
|
8371
|
+
type: "string",
|
|
8372
|
+
description: "key=PLACEHOLDER filled in per ad, repeatable \u2014 e.g. utm_campaign=CAMPAIGN_NAME",
|
|
8373
|
+
required: false
|
|
8374
|
+
},
|
|
8375
|
+
clear: { type: "boolean", description: "Remove every tracking parameter from this ad set", required: false },
|
|
8376
|
+
file: { type: "string", description: "JSON payload file; flags override file keys", required: false }
|
|
8377
|
+
}
|
|
8378
|
+
});
|
|
8308
8379
|
var statusSugarArgs = {
|
|
8309
8380
|
id: { type: "positional", description: "Entity id or URN", required: true },
|
|
8310
8381
|
...writeAccountArgs
|
|
@@ -9829,6 +9900,88 @@ Example: baker ads linkedin ${entity} ${name} 123456`
|
|
|
9829
9900
|
}
|
|
9830
9901
|
});
|
|
9831
9902
|
}
|
|
9903
|
+
function trackingPairs(value, flag) {
|
|
9904
|
+
const raw = value === void 0 ? [] : Array.isArray(value) ? value : [value];
|
|
9905
|
+
return raw.flatMap(
|
|
9906
|
+
(entry) => String(entry).split("&").filter((pair) => pair.length > 0).map((pair) => {
|
|
9907
|
+
const at = pair.indexOf("=");
|
|
9908
|
+
if (at < 1 || at === pair.length - 1) {
|
|
9909
|
+
failWriteValidation2(`${flag} expects key=value pairs \u2014 got "${pair}"`);
|
|
9910
|
+
}
|
|
9911
|
+
return [pair.slice(0, at).trim(), pair.slice(at + 1).trim()];
|
|
9912
|
+
})
|
|
9913
|
+
);
|
|
9914
|
+
}
|
|
9915
|
+
function asDynamicValue(value) {
|
|
9916
|
+
const bare = value.replace(/^\{+|\}+$/g, "").toUpperCase();
|
|
9917
|
+
return TRACKING_PARAM_DYNAMIC_VALUES.includes(bare) ? bare : void 0;
|
|
9918
|
+
}
|
|
9919
|
+
function trackingParamsPayload(args) {
|
|
9920
|
+
if (args.clear) {
|
|
9921
|
+
return { dynamicValueParameters: {}, customValueParameters: {} };
|
|
9922
|
+
}
|
|
9923
|
+
const customValueParameters = {};
|
|
9924
|
+
for (const [key, value] of trackingPairs(args.param, "--param")) {
|
|
9925
|
+
if (asDynamicValue(value)) {
|
|
9926
|
+
failWriteValidation2(
|
|
9927
|
+
`--param ${key}=${value} names a value LinkedIn fills in per ad \u2014 pass it as --dynamic ${key}=${value.replace(/^\{+|\}+$/g, "").toUpperCase()}`
|
|
9928
|
+
);
|
|
9929
|
+
}
|
|
9930
|
+
customValueParameters[key] = value;
|
|
9931
|
+
}
|
|
9932
|
+
const dynamicValueParameters = {};
|
|
9933
|
+
for (const [key, value] of trackingPairs(args.dynamic, "--dynamic")) {
|
|
9934
|
+
const resolved = asDynamicValue(value);
|
|
9935
|
+
if (!resolved) {
|
|
9936
|
+
failWriteValidation2(
|
|
9937
|
+
`--dynamic ${key}=${value} is not a value LinkedIn can fill in \u2014 use one of ${TRACKING_PARAM_DYNAMIC_VALUES.join(", ")}, or pass a fixed value with --param`
|
|
9938
|
+
);
|
|
9939
|
+
}
|
|
9940
|
+
dynamicValueParameters[key] = resolved;
|
|
9941
|
+
}
|
|
9942
|
+
return mergePayload2(loadJsonFileArg2(args.file), {
|
|
9943
|
+
customValueParameters: Object.keys(customValueParameters).length > 0 ? customValueParameters : void 0,
|
|
9944
|
+
dynamicValueParameters: Object.keys(dynamicValueParameters).length > 0 ? dynamicValueParameters : void 0
|
|
9945
|
+
});
|
|
9946
|
+
}
|
|
9947
|
+
var campaignsUrlParamsCommand = defineCommand37({
|
|
9948
|
+
meta: {
|
|
9949
|
+
name: "url-params",
|
|
9950
|
+
description: `Stage the URL tracking parameters on an ad set \u2014 LinkedIn appends them to the link of EVERY ad in it, including ads already running. ${STAGED_NOTE}
|
|
9951
|
+
|
|
9952
|
+
Start here: set UTMs at this level, not per ad. A per-ad landing URL is only for a genuinely different destination \u2014 LinkedIn appends the ad set's parameters on top of whatever the ad's own URL carries, so the same key set in both places lands twice.
|
|
9953
|
+
|
|
9954
|
+
Values LinkedIn fills in per ad (--dynamic): ACCOUNT_ID, ACCOUNT_NAME, CAMPAIGN_GROUP_ID, CAMPAIGN_GROUP_NAME, CAMPAIGN_ID, CAMPAIGN_NAME, CREATIVE_ID, CREATIVE_NAME.
|
|
9955
|
+
|
|
9956
|
+
Examples:
|
|
9957
|
+
baker ads linkedin campaigns url-params 123456 --param "utm_source=linkedin&utm_medium=paid-social" --dynamic utm_campaign=CAMPAIGN_NAME --dynamic utm_content=CREATIVE_ID
|
|
9958
|
+
baker ads linkedin campaigns url-params 123456 --param utm_agency=baker
|
|
9959
|
+
baker ads linkedin campaigns url-params 123456 --clear`
|
|
9960
|
+
},
|
|
9961
|
+
args: {
|
|
9962
|
+
id: { type: "positional", description: "Ad set (campaign) id or URN", required: true },
|
|
9963
|
+
...accountArgs,
|
|
9964
|
+
param: {
|
|
9965
|
+
type: "string",
|
|
9966
|
+
description: 'Fixed key=value, repeatable or &-joined (e.g. --param "utm_source=linkedin&utm_medium=paid")'
|
|
9967
|
+
},
|
|
9968
|
+
dynamic: {
|
|
9969
|
+
type: "string",
|
|
9970
|
+
description: "key=PLACEHOLDER LinkedIn fills in per ad, repeatable (e.g. --dynamic utm_campaign=CAMPAIGN_NAME)"
|
|
9971
|
+
},
|
|
9972
|
+
clear: { type: "boolean", description: "Remove every tracking parameter from this ad set" },
|
|
9973
|
+
file: { type: "string", description: "JSON file with the full payload; flags override file keys" }
|
|
9974
|
+
},
|
|
9975
|
+
run: async ({ args }) => {
|
|
9976
|
+
const accountId = bareAccountId(args);
|
|
9977
|
+
await stageOp({
|
|
9978
|
+
kind: "trackingParams.set",
|
|
9979
|
+
accountId,
|
|
9980
|
+
target: requireTarget2(args, "ad set"),
|
|
9981
|
+
payload: trackingParamsPayload(args)
|
|
9982
|
+
});
|
|
9983
|
+
}
|
|
9984
|
+
});
|
|
9832
9985
|
var campaignsPauseCommand = statusSugarCommand("campaigns", "campaign.update", "pause");
|
|
9833
9986
|
var campaignsResumeCommand = statusSugarCommand("campaigns", "campaign.update", "resume");
|
|
9834
9987
|
var campaignsArchiveCommand = statusSugarCommand("campaigns", "campaign.update", "archive");
|
|
@@ -10361,7 +10514,8 @@ Examples:
|
|
|
10361
10514
|
baker ads linkedin campaigns --account-id 503001492
|
|
10362
10515
|
baker ads linkedin campaigns --account-id 503001492 --all-statuses --output csv
|
|
10363
10516
|
baker ads linkedin campaigns update 123456 --daily-budget 100 --currency EUR
|
|
10364
|
-
baker ads linkedin campaigns pause 123456
|
|
10517
|
+
baker ads linkedin campaigns pause 123456
|
|
10518
|
+
baker ads linkedin campaigns url-params 123456 --param utm_source=linkedin \u2014 UTMs for every ad in the ad set`
|
|
10365
10519
|
},
|
|
10366
10520
|
subCommands: {
|
|
10367
10521
|
create: campaignsCreateCommand,
|
|
@@ -10369,7 +10523,8 @@ Examples:
|
|
|
10369
10523
|
pause: campaignsPauseCommand,
|
|
10370
10524
|
resume: campaignsResumeCommand,
|
|
10371
10525
|
archive: campaignsArchiveCommand,
|
|
10372
|
-
duplicate: campaignsDuplicateCommand
|
|
10526
|
+
duplicate: campaignsDuplicateCommand,
|
|
10527
|
+
"url-params": campaignsUrlParamsCommand
|
|
10373
10528
|
},
|
|
10374
10529
|
args: {
|
|
10375
10530
|
"account-id": { type: "string", description: "Numeric account ID or urn:li:sponsoredAccount:N" },
|