@koda-sl/baker-cli 0.163.0 → 0.164.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 CHANGED
@@ -502,7 +502,17 @@ Reference not-yet-created resources with `g_temp_*` refs returned by earlier `cr
502
502
  baker ads google budgets create --customer-id 1234567890 --name "Search budget" --amount 50
503
503
  # → { ref: "g_temp_ab12", ... }
504
504
  baker ads google campaigns create --customer-id 1234567890 --name "Brand — Search" \
505
- --channel-type SEARCH --budget-ref g_temp_ab12 --bidding-strategy MANUAL_CPC
505
+ --channel-type SEARCH --budget-ref g_temp_ab12 --bidding-strategy MANUAL_CPC \
506
+ --geo-target-type PRESENCE
507
+ # --geo-target-type is the campaign's location option: PRESENCE (only people in your targeted
508
+ # locations) or PRESENCE_OR_INTEREST (also people interested in them). Google applies
509
+ # PRESENCE_OR_INTEREST when the flag is omitted, so pass PRESENCE for local/in-store/service-area
510
+ # campaigns; --negative-geo-target-type does the same for excluded locations.
511
+ # Campaign URL options stage on create and update: --final-url-suffix (bare query params),
512
+ # --tracking-template "https://tracker.example/?url={lpurl}" (must carry {lpurl}), and
513
+ # --custom-param key=value (repeatable, up to 8; --clear-custom-params empties the set).
514
+ # "" clears the suffix or template back to the account value. The ACCOUNT level is not
515
+ # stageable — Google writes it through a different service than the atomic publish batch.
506
516
  # New campaigns are declared as NOT containing EU political advertising (Google requires the
507
517
  # declaration on every create); pass --eu-political-ads only when the campaign does contain it.
508
518
  # On update, --eu-political-ads / --eu-political-ads=false corrects an existing declaration.
package/dist/cli.js CHANGED
@@ -4489,7 +4489,13 @@ var GOOGLE_ADS_LIMITS = {
4489
4489
  campaign: {
4490
4490
  nameMax: 255,
4491
4491
  /** `campaign.final_url_suffix` — the query string appended to every final URL under the campaign. */
4492
- finalUrlSuffixMax: 2048
4492
+ finalUrlSuffixMax: 2048,
4493
+ /** `campaign.tracking_url_template` — the click-measurement URL every ad routes through. */
4494
+ trackingUrlTemplateMax: 2048,
4495
+ /** `campaign.url_custom_parameters` — Google caps a single entity at 8 `{_name}` parameters. */
4496
+ urlCustomParametersMax: 8,
4497
+ urlCustomParameterKeyMax: 16,
4498
+ urlCustomParameterValueMax: 250
4493
4499
  },
4494
4500
  adGroup: {
4495
4501
  nameMax: 255
@@ -4609,6 +4615,8 @@ var BIDDING_STRATEGY_TYPES = [
4609
4615
  "MANUAL_CPV",
4610
4616
  "PERCENT_CPC"
4611
4617
  ];
4618
+ var POSITIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
4619
+ var NEGATIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
4612
4620
  var BUDGET_DELIVERY_METHODS = ["STANDARD", "ACCELERATED"];
4613
4621
  var AD_GROUP_TYPES = [
4614
4622
  "SEARCH_STANDARD",
@@ -4704,6 +4712,23 @@ var customerIdSchema = z13.string().regex(NUMERIC_ID_REGEX2, "customerId must be
4704
4712
  var stageableStatusSchema2 = z13.enum(STAGEABLE_CREATE_STATUSES2);
4705
4713
  var matchTypeSchema = z13.enum(KEYWORD_MATCH_TYPES);
4706
4714
  var finalUrlSuffixSchema = z13.string().max(GOOGLE_ADS_LIMITS.campaign.finalUrlSuffixMax).refine((s) => !/^[?&]/.test(s), "drop the leading ? or & \u2014 a final URL suffix is bare query parameters").refine((s) => !s.includes("{lpurl}"), "{lpurl} belongs in a tracking template, not in a final URL suffix").refine((s) => !/\s/.test(s), "a final URL suffix cannot contain whitespace").refine((s) => s === "" || s.includes("="), 'expected key=value pairs, e.g. "utm_source=google&utm_agency=baker"');
4715
+ var LANDING_PAGE_TAGS = ["{lpurl}", "{unescapedlpurl}", "{escapedlpurl}", "{lpurl+2}", "{lpurl+3}"];
4716
+ var trackingUrlTemplateSchema = z13.string().max(GOOGLE_ADS_LIMITS.campaign.trackingUrlTemplateMax).refine((t) => !/\s/.test(t), "a tracking template cannot contain whitespace").refine(
4717
+ (t) => t === "" || LANDING_PAGE_TAGS.some((tag) => t.includes(tag)),
4718
+ `a tracking template must carry the landing page through one of ${LANDING_PAGE_TAGS.join(", ")}, e.g. "https://tracker.example/?url={lpurl}"`
4719
+ ).refine(
4720
+ (t) => t === "" || /^(https?:\/\/|\{)/.test(t),
4721
+ "a tracking template must start with http://, https:// or a {lpurl} tag"
4722
+ );
4723
+ var urlCustomParametersSchema = z13.array(
4724
+ z13.strictObject({
4725
+ key: z13.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.urlCustomParameterKeyMax).regex(/^[A-Za-z0-9_]+$/, "a custom parameter key is letters, digits and underscores only"),
4726
+ value: z13.string().max(GOOGLE_ADS_LIMITS.campaign.urlCustomParameterValueMax)
4727
+ })
4728
+ ).max(GOOGLE_ADS_LIMITS.campaign.urlCustomParametersMax).refine(
4729
+ (params) => new Set(params.map((p) => p.key)).size === params.length,
4730
+ "each custom parameter key can appear only once"
4731
+ );
4707
4732
  var keywordTextSchema = z13.string().min(1).max(GOOGLE_ADS_LIMITS.keyword.textMax).refine((t) => t.trim().split(/\s+/).length <= GOOGLE_ADS_LIMITS.keyword.wordsMax, "keyword exceeds 10 words");
4708
4733
  var budgetCreateSchema = z13.object({
4709
4734
  name: z13.string().min(1).max(GOOGLE_ADS_LIMITS.budget.nameMax),
@@ -4737,7 +4762,14 @@ var networkSettingsSchema = z13.object({
4737
4762
  targetPartnerSearchNetwork: z13.boolean().optional()
4738
4763
  });
4739
4764
  var dateSchema = z13.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected a YYYY-MM-DD date");
4740
- var campaignCreateSchema2 = z13.object({
4765
+ var geoTargetTypeSettingSchema = z13.strictObject({
4766
+ positiveGeoTargetType: z13.enum(POSITIVE_GEO_TARGET_TYPES).optional(),
4767
+ negativeGeoTargetType: z13.enum(NEGATIVE_GEO_TARGET_TYPES).optional()
4768
+ }).refine(
4769
+ (p) => p.positiveGeoTargetType !== void 0 || p.negativeGeoTargetType !== void 0,
4770
+ "geoTargetTypeSetting needs positiveGeoTargetType and/or negativeGeoTargetType"
4771
+ );
4772
+ var campaignCreateSchema2 = z13.strictObject({
4741
4773
  name: z13.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax),
4742
4774
  channelType: z13.enum(ADVERTISING_CHANNEL_TYPES),
4743
4775
  channelSubType: z13.enum(ADVERTISING_CHANNEL_SUB_TYPES).optional(),
@@ -4746,10 +4778,19 @@ var campaignCreateSchema2 = z13.object({
4746
4778
  bidding: biddingConfigSchema.optional(),
4747
4779
  biddingStrategy: refSchema.optional(),
4748
4780
  networkSettings: networkSettingsSchema.optional(),
4781
+ /**
4782
+ * "Location options". Omitted stages Google's own default — reach people *in or interested in*
4783
+ * the targeted locations — so pass `PRESENCE` explicitly when only people physically there count.
4784
+ */
4785
+ geoTargetTypeSetting: geoTargetTypeSettingSchema.optional(),
4749
4786
  startDate: dateSchema.optional(),
4750
4787
  endDate: dateSchema.optional(),
4751
4788
  /** Campaign-level override of the account's final URL suffix — REPLACES it, never merges. */
4752
4789
  finalUrlSuffix: finalUrlSuffixSchema.optional(),
4790
+ /** Campaign-level override of the account's tracking template — REPLACES it, never merges. */
4791
+ trackingUrlTemplate: trackingUrlTemplateSchema.optional(),
4792
+ /** The `{_name}` parameters this campaign's tracking template and final URLs can reference. */
4793
+ urlCustomParameters: urlCustomParametersSchema.optional(),
4753
4794
  /** Advisory Google Ads UI objective — drives warnings, not sent to the API. */
4754
4795
  objective: z13.enum(CAMPAIGN_OBJECTIVES).optional(),
4755
4796
  /**
@@ -4780,15 +4821,21 @@ var campaignCreateSchema2 = z13.object({
4780
4821
  ctx.addIssue({ code: "custom", path: ["endDate"], message: "endDate must be after startDate" });
4781
4822
  }
4782
4823
  });
4783
- var campaignUpdateSchema2 = z13.object({
4824
+ var campaignUpdateSchema2 = z13.strictObject({
4784
4825
  name: z13.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax).optional(),
4785
4826
  budget: refSchema.optional(),
4786
4827
  bidding: biddingConfigSchema.optional(),
4787
4828
  networkSettings: networkSettingsSchema.optional(),
4829
+ /** Switches an existing campaign between "Presence or interest" and "Presence". */
4830
+ geoTargetTypeSetting: geoTargetTypeSettingSchema.optional(),
4788
4831
  startDate: dateSchema.optional(),
4789
4832
  endDate: dateSchema.optional(),
4790
4833
  /** Campaign-level override of the account's final URL suffix. `""` clears it back to the account value. */
4791
4834
  finalUrlSuffix: finalUrlSuffixSchema.optional(),
4835
+ /** Campaign-level tracking template. `""` clears it back to the account value. */
4836
+ trackingUrlTemplate: trackingUrlTemplateSchema.optional(),
4837
+ /** Replaces the campaign's custom parameters wholesale; `[]` removes them all. */
4838
+ urlCustomParameters: urlCustomParametersSchema.optional(),
4792
4839
  /** Corrects the campaign's EU political advertising declaration (true = contains, false = does not). */
4793
4840
  euPoliticalAds: z13.boolean().optional(),
4794
4841
  status: z13.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
@@ -6947,8 +6994,8 @@ async function stageGoogleOp(raw, hints) {
6947
6994
  handleGoogleError(err);
6948
6995
  }
6949
6996
  }
6950
- async function stageCreate(kind, customerId, payload) {
6951
- await stageGoogleOp({ kind, customerId, payload });
6997
+ async function stageCreate(kind, customerId, payload, hints) {
6998
+ await stageGoogleOp({ kind, customerId, payload }, hints);
6952
6999
  }
6953
7000
  async function stageGoogleOps(rawOps, hints) {
6954
7001
  if (rawOps.length === 1 && rawOps[0]) {
@@ -8451,6 +8498,81 @@ var finalUrlSuffixArg = {
8451
8498
  description: 'Final URL suffix for this campaign \u2014 bare query params, no leading "?" (e.g. "utm_source=google&utm_agency=baker"). REPLACES the account-level suffix for this campaign; pass "" to clear it back to the account value. Account-wide tagging belongs at the account level: --level account tells you where.'
8452
8499
  }
8453
8500
  };
8501
+ var urlOptionsArgs = {
8502
+ "tracking-template": {
8503
+ type: "string",
8504
+ description: 'Campaign tracking template \u2014 the click-measurement URL every ad routes through. Must carry the landing page through {lpurl}, e.g. "https://tracker.example/?url={lpurl}". REPLACES the account-level template for this campaign; pass "" to clear it back to the account value.'
8505
+ },
8506
+ "custom-param": {
8507
+ type: "string",
8508
+ description: "Custom parameter as key=value, referenced in a template or final URL as {_key} (e.g. --custom-param season=summer). Repeat for several; Google allows 8. REPLACES the campaign's whole set."
8509
+ },
8510
+ "clear-custom-params": {
8511
+ type: "boolean",
8512
+ description: "Remove every custom parameter from this campaign (Google replaces the set wholesale)."
8513
+ }
8514
+ };
8515
+ function urlCustomParametersFromFlags(args) {
8516
+ const raw = args["custom-param"];
8517
+ const entries = (Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : []).filter(
8518
+ (v) => typeof v === "string" && v.length > 0
8519
+ );
8520
+ if (entries.length === 0) {
8521
+ return args["clear-custom-params"] === true ? [] : void 0;
8522
+ }
8523
+ if (args["clear-custom-params"] === true) {
8524
+ failWriteValidation("--clear-custom-params removes every parameter \u2014 pass it OR --custom-param, not both");
8525
+ }
8526
+ return entries.map((entry) => {
8527
+ const split = entry.indexOf("=");
8528
+ if (split <= 0) {
8529
+ failWriteValidation(`--custom-param "${entry}" must be key=value (e.g. --custom-param season=summer)`);
8530
+ }
8531
+ return { key: entry.slice(0, split), value: entry.slice(split + 1) };
8532
+ });
8533
+ }
8534
+ var geoTargetTypeArgs = {
8535
+ "geo-target-type": {
8536
+ type: "string",
8537
+ description: 'Location option \u2014 PRESENCE (only people in your targeted locations) or PRESENCE_OR_INTEREST (also people interested in them). Google applies PRESENCE_OR_INTEREST when this is left out, so pass PRESENCE explicitly for local-service, delivery-radius or in-store campaigns. Sets the same "Location options" as the Google Ads UI.'
8538
+ },
8539
+ "negative-geo-target-type": {
8540
+ type: "string",
8541
+ description: "How EXCLUDED locations are matched \u2014 PRESENCE (default: excludes people in the location) or PRESENCE_OR_INTEREST (also excludes people interested in it)."
8542
+ }
8543
+ };
8544
+ function enumFlag(value, flag, allowed) {
8545
+ if (typeof value !== "string" || value.length === 0) {
8546
+ return void 0;
8547
+ }
8548
+ const upper2 = value.toUpperCase();
8549
+ if (!allowed.includes(upper2)) {
8550
+ failWriteValidation(`${flag} must be one of ${allowed.join(" | ")}`);
8551
+ }
8552
+ return upper2;
8553
+ }
8554
+ function geoTargetTypeFromFlags(args) {
8555
+ const positiveGeoTargetType = enumFlag(args["geo-target-type"], "--geo-target-type", POSITIVE_GEO_TARGET_TYPES);
8556
+ const negativeGeoTargetType = enumFlag(
8557
+ args["negative-geo-target-type"],
8558
+ "--negative-geo-target-type",
8559
+ NEGATIVE_GEO_TARGET_TYPES
8560
+ );
8561
+ if (positiveGeoTargetType === void 0 && negativeGeoTargetType === void 0) {
8562
+ return void 0;
8563
+ }
8564
+ return Object.fromEntries(
8565
+ Object.entries({ positiveGeoTargetType, negativeGeoTargetType }).filter(([, v]) => v !== void 0)
8566
+ );
8567
+ }
8568
+ function geoTargetTypeHints(payload) {
8569
+ if (payload.geoTargetTypeSetting !== void 0) {
8570
+ return [];
8571
+ }
8572
+ return [
8573
+ `No --geo-target-type set, so this campaign will use Google's default location option "Presence or interest" \u2014 it reaches people interested in your locations, not only people in them. Re-stage with --geo-target-type PRESENCE if the offer is local (in-store, delivery radius, service area), and tell the user which of the two this campaign uses.`
8574
+ ];
8575
+ }
8454
8576
  function accountLevelNotStageable(customerId, suffix) {
8455
8577
  writeJsonEnvelope({
8456
8578
  ok: false,
@@ -8494,6 +8616,8 @@ var campaignsCommand = defineCommand30({
8494
8616
  "target-roas": { type: "string", description: "Target ROAS (e.g. 4.0) for TARGET_ROAS" },
8495
8617
  ...maxCpcArg,
8496
8618
  ...finalUrlSuffixArg,
8619
+ ...urlOptionsArgs,
8620
+ ...geoTargetTypeArgs,
8497
8621
  objective: { type: "string", description: "Advisory UI objective (SALES, LEADS, \u2026)" },
8498
8622
  "start-date": { type: "string", description: "YYYY-MM-DD" },
8499
8623
  "end-date": { type: "string", description: "YYYY-MM-DD" },
@@ -8511,14 +8635,17 @@ var campaignsCommand = defineCommand30({
8511
8635
  channelSubType: args["sub-type"],
8512
8636
  budget: args["budget-ref"],
8513
8637
  bidding: biddingFromFlags(args),
8638
+ geoTargetTypeSetting: geoTargetTypeFromFlags(args),
8514
8639
  finalUrlSuffix: args["final-url-suffix"],
8640
+ trackingUrlTemplate: args["tracking-template"],
8641
+ urlCustomParameters: urlCustomParametersFromFlags(args),
8515
8642
  objective: args.objective,
8516
8643
  startDate: args["start-date"],
8517
8644
  endDate: args["end-date"],
8518
8645
  euPoliticalAds: args["eu-political-ads"],
8519
8646
  status: args.status
8520
8647
  });
8521
- await stageCreate("google.campaign.create", customerId, payload);
8648
+ await stageCreate("google.campaign.create", customerId, payload, geoTargetTypeHints(payload));
8522
8649
  }
8523
8650
  }),
8524
8651
  update: defineCommand30({
@@ -8533,6 +8660,8 @@ var campaignsCommand = defineCommand30({
8533
8660
  "target-roas": { type: "string" },
8534
8661
  ...maxCpcArg,
8535
8662
  ...finalUrlSuffixArg,
8663
+ ...urlOptionsArgs,
8664
+ ...geoTargetTypeArgs,
8536
8665
  "eu-political-ads": {
8537
8666
  type: "boolean",
8538
8667
  description: "Corrects the campaign's EU political advertising declaration: --eu-political-ads if it contains EU political ads, --eu-political-ads=false if it does not. Existing campaigns without a declaration block location-targeting changes until one is set."
@@ -8557,7 +8686,10 @@ var campaignsCommand = defineCommand30({
8557
8686
  name: args.name,
8558
8687
  budget: args["budget-ref"],
8559
8688
  bidding: biddingFromFlags(args),
8689
+ geoTargetTypeSetting: geoTargetTypeFromFlags(args),
8560
8690
  finalUrlSuffix: args["final-url-suffix"],
8691
+ trackingUrlTemplate: args["tracking-template"],
8692
+ urlCustomParameters: urlCustomParametersFromFlags(args),
8561
8693
  euPoliticalAds: args["eu-political-ads"],
8562
8694
  status: args.status
8563
8695
  });