@koda-sl/baker-cli 0.255.0 → 0.257.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 +5 -1
- package/dist/cli.js +63 -17
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3024,7 +3024,7 @@ baker scheduled-actions create --template baker-ads --name "Weekly ad review" --
|
|
|
3024
3024
|
Rules:
|
|
3025
3025
|
|
|
3026
3026
|
- `--cron` and `--run-at` are mutually exclusive.
|
|
3027
|
-
- `--run-at` must be an ISO UTC timestamp ending in `Z
|
|
3027
|
+
- `--run-at` must be an ISO UTC timestamp ending in `Z`, and still in the future — a time that has already passed is refused when the op is staged. If a one-off's time passes while the chat is still open, publishing creates the task **switched off** with the reason on it rather than dropping it, so it is never lost.
|
|
3028
3028
|
- Draft-created ids use `temp_sched_*`; use them with `get`, `update`, or `delete` before publish.
|
|
3029
3029
|
- Staged commands require `BAKER_CHAT_ID` to point to an `in_progress` chat.
|
|
3030
3030
|
- `trigger` rejects `temp_sched_*` and only accepts published scheduled-action ids.
|
|
@@ -5843,6 +5843,10 @@ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions wo
|
|
|
5843
5843
|
|
|
5844
5844
|
Three more things the 202608 pin brought with it. **The conversion-type enum went from 8 values to all 34 LinkedIn accepts** — `QUALIFIED_LEAD`, `BOOK_APPOINTMENT`, `SUBMIT_APPLICATION` and 23 others were rejected by our own schema before the request ever left, and 202608 adds `MARKETING_QUALIFIED_LEAD` / `SALES_QUALIFIED_LEAD` on top. **Attribution windows gained 365** (and view-through gained 90), with the type-conditional rule enforced on `conversions create`: 365 is only legal for `SUBMIT_APPLICATION`, `PURCHASE`, `ADD_TO_CART`, `QUALIFIED_LEAD`, `LEAD`. And **a 429 carrying `SEGMENT_LIMIT_EXCEEDED` is no longer reported as a throttle** — 202608 caps an ad account at 1,000 DMP segments and reuses the rate-limit status for it, so an agent that read it as "retry in a second" would have retried forever against a cap only a human can clear in Campaign Manager.
|
|
5845
5845
|
|
|
5846
|
+
- **0.256.0**: **`assets attach` no longer offers an image link, because Google accepts none.** A production publish came back with six `AssetLinkError.FIELD_TYPE_INCOMPATIBLE_WITH_CAMPAIGN_TYPE` rows (trigger `MARKETING_IMAGE,SEARCH`) on an otherwise built and live Search campaign — staged exactly as this CLI's own advisory and the scaffold's ads-google doc instructed, at `"fieldType": "MARKETING_IMAGE"`. The obvious fix was `AD_IMAGE`, which is what Google's documentation and its own forum answers name for a Search image extension. **It does not work either.** Sent to a real account on v23, all four combinations are refused: `MARKETING_IMAGE` on a Search `CampaignAsset` → `FIELD_TYPE_INCOMPATIBLE_WITH_CAMPAIGN_TYPE`; `MARKETING_IMAGE` on a Search `AdGroupAsset` → `UNSUPPORTED_FIELD_TYPE`; `AD_IMAGE` on a `CampaignAsset` → `UNSUPPORTED_FIELD_TYPE`; `AD_IMAGE` on an `AdGroupAsset` → `UNSUPPORTED_FIELD_TYPE`. Image extensions never moved to asset-based extensions, so there is no request the API takes. Every image field type is now refused at the CLI boundary with the interface named instead of another field type to try, and the **"recommend at least 3 image assets" advisory is removed** — it had been firing on every Search campaign since 0.191.0 and could not be satisfied by any draft, because the write it named has never once succeeded. Performance Max asset-group images are a different mechanism and are untouched (`asset-groups attach`). Four force-synced scaffold surfaces were saying the same wrong thing and now agree with the API: the `ads-google.md` tool doc, the `assets` command group description, the account-audit playbook's Dynamic Images row, and `staged-writes.md`. An audit should still raise a missing-images gap — it is a real SERP disadvantage — it just has to name the Google Ads interface as the fix rather than a command.
|
|
5847
|
+
|
|
5848
|
+
- **0.256.0**: a structured snippet value over 25 characters is refused before it is staged. Google caps each VALUE at 25 and answers a longer one with a bare `Too long.` at `structured_snippet_asset.values[i]` — no header, no value, nothing to act on — and it takes the asset's campaign link down with it as `RESOURCE_NOT_FOUND`. `assets create --values` and `assets update --values` now cap each entry; the per-asset 3-10 count check was already there, and only the per-value length was missing. Found on a live publish where four of five values were fine and "Bases de datos vectoriales" (26) was not, and confirmed fixed by re-publishing the same snippet at 25 characters against a real account, where it applied and linked cleanly.
|
|
5849
|
+
|
|
5846
5850
|
### Testing a pre-release in sandboxes
|
|
5847
5851
|
|
|
5848
5852
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -2113,7 +2113,14 @@ var GOOGLE_ADS_LIMITS = {
|
|
|
2113
2113
|
// Google's published headers (`structured-snippet-headers.ts`), the longest
|
|
2114
2114
|
// of which is 29 characters. Only the values are capped.
|
|
2115
2115
|
structuredSnippetValuesMin: 3,
|
|
2116
|
-
structuredSnippetValuesMax: 10
|
|
2116
|
+
structuredSnippetValuesMax: 10,
|
|
2117
|
+
/**
|
|
2118
|
+
* Per VALUE, not per asset. Google rejects a 26-character value with a bare "Too long." at
|
|
2119
|
+
* `structured_snippet_asset.values[i]` — one red row inside an otherwise clean publish, plus
|
|
2120
|
+
* every link that depends on the asset. Confirmed live on v23: "Bases de datos vectoriales"
|
|
2121
|
+
* (26) rejected, its four neighbours accepted.
|
|
2122
|
+
*/
|
|
2123
|
+
structuredSnippetValueMax: 25
|
|
2117
2124
|
},
|
|
2118
2125
|
conversionAction: {
|
|
2119
2126
|
nameMax: 255
|
|
@@ -2296,9 +2303,24 @@ var ASSET_FIELD_TYPES = [
|
|
|
2296
2303
|
"HEADLINE",
|
|
2297
2304
|
"DESCRIPTION",
|
|
2298
2305
|
"LOGO",
|
|
2306
|
+
/**
|
|
2307
|
+
* Kept because a draft staged before {@link IMAGE_ASSET_LINK_MESSAGE} shipped may still carry one,
|
|
2308
|
+
* and because it is the field type an asset-group image occupies. It is refused on an
|
|
2309
|
+
* `assetLink.attach` — see that message for what Google actually does with it.
|
|
2310
|
+
*/
|
|
2299
2311
|
"MARKETING_IMAGE",
|
|
2300
2312
|
"BUSINESS_NAME"
|
|
2301
2313
|
];
|
|
2314
|
+
var IMAGE_ASSET_LINK_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
2315
|
+
"MARKETING_IMAGE",
|
|
2316
|
+
"SQUARE_MARKETING_IMAGE",
|
|
2317
|
+
"PORTRAIT_MARKETING_IMAGE",
|
|
2318
|
+
"AD_IMAGE"
|
|
2319
|
+
]);
|
|
2320
|
+
var IMAGE_ASSET_LINK_MESSAGE = "Google does not accept an image through an asset link. MARKETING_IMAGE is a Performance Max asset-group slot (use `asset-groups attach`), and AD_IMAGE \u2014 the field type Google names for a Search image extension \u2014 comes back UNSUPPORTED_FIELD_TYPE at campaign and ad group level alike. Image extensions are not reachable through the API: tell the user to add them in the Google Ads interface, and carry on with the rest of the job.";
|
|
2321
|
+
function isImageAssetLinkFieldType(fieldType) {
|
|
2322
|
+
return IMAGE_ASSET_LINK_FIELD_TYPES.has(fieldType);
|
|
2323
|
+
}
|
|
2302
2324
|
var ASSET_GROUP_ASSET_FIELD_TYPES = [
|
|
2303
2325
|
"MARKETING_IMAGE",
|
|
2304
2326
|
"SQUARE_MARKETING_IMAGE",
|
|
@@ -3151,7 +3173,7 @@ var structuredSnippetHeaderSchema = z4.string().min(1).transform((header, ctx) =
|
|
|
3151
3173
|
var structuredSnippetAssetSchema = z4.object({
|
|
3152
3174
|
type: z4.literal("structuredSnippet"),
|
|
3153
3175
|
header: structuredSnippetHeaderSchema,
|
|
3154
|
-
values: z4.array(z4.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax)
|
|
3176
|
+
values: z4.array(z4.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValueMax)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax)
|
|
3155
3177
|
});
|
|
3156
3178
|
var callToActionAssetSchema = z4.object({ type: z4.literal("callToAction"), callToAction: z4.string().min(1) });
|
|
3157
3179
|
var assetCreateSchema = z4.discriminatedUnion("type", [
|
|
@@ -3171,7 +3193,7 @@ var assetUpdateSchema = z4.object({
|
|
|
3171
3193
|
finalUrls: z4.array(httpsUrlSchema2).min(1).optional(),
|
|
3172
3194
|
calloutText: z4.string().min(1).max(GOOGLE_ADS_LIMITS.asset.calloutTextMax).optional(),
|
|
3173
3195
|
header: structuredSnippetHeaderSchema.optional(),
|
|
3174
|
-
values: z4.array(z4.string().min(1)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax).optional(),
|
|
3196
|
+
values: z4.array(z4.string().min(1).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValueMax)).min(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMin).max(GOOGLE_ADS_LIMITS.asset.structuredSnippetValuesMax).optional(),
|
|
3175
3197
|
callToAction: z4.string().min(1).optional(),
|
|
3176
3198
|
text: z4.string().min(1).optional()
|
|
3177
3199
|
}).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
|
|
@@ -3179,7 +3201,11 @@ var assetLinkAttachSchema = z4.object({
|
|
|
3179
3201
|
level: z4.enum(["campaign", "adGroup", "customer"]),
|
|
3180
3202
|
parent: refSchema.optional(),
|
|
3181
3203
|
asset: refSchema,
|
|
3182
|
-
|
|
3204
|
+
// A plain string, not `z.enum(ASSET_FIELD_TYPES)`, so the image refusal below can answer with
|
|
3205
|
+
// the reason instead of an enum listing. An agent reaching for AD_IMAGE is following Google's
|
|
3206
|
+
// own documentation; "Invalid option: expected one of …" sends it to guess another field type,
|
|
3207
|
+
// which is the failure this whole refusal exists to end.
|
|
3208
|
+
fieldType: z4.string().min(1)
|
|
3183
3209
|
}).superRefine((value, ctx) => {
|
|
3184
3210
|
if (value.level !== "customer" && !value.parent) {
|
|
3185
3211
|
ctx.addIssue({
|
|
@@ -3188,6 +3214,15 @@ var assetLinkAttachSchema = z4.object({
|
|
|
3188
3214
|
message: `parent is required for a ${value.level}-level asset link (--parent-ref)`
|
|
3189
3215
|
});
|
|
3190
3216
|
}
|
|
3217
|
+
if (isImageAssetLinkFieldType(value.fieldType)) {
|
|
3218
|
+
ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["fieldType"], message: IMAGE_ASSET_LINK_MESSAGE });
|
|
3219
|
+
} else if (!ASSET_FIELD_TYPES.includes(value.fieldType)) {
|
|
3220
|
+
ctx.addIssue({
|
|
3221
|
+
code: z4.ZodIssueCode.custom,
|
|
3222
|
+
path: ["fieldType"],
|
|
3223
|
+
message: `expected one of ${ASSET_FIELD_TYPES.join(" | ")}`
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3191
3226
|
});
|
|
3192
3227
|
var assetGroupCreateSchema = z4.object({
|
|
3193
3228
|
campaign: refSchema,
|
|
@@ -14861,7 +14896,7 @@ var assetsUpdateCommand = defineCommand30({
|
|
|
14861
14896
|
"final-urls": { type: "string", description: "Comma-separated sitelink final URLs" },
|
|
14862
14897
|
"callout-text": { type: "string", description: "Callout text" },
|
|
14863
14898
|
header: { type: "string", description: "Structured snippet header" },
|
|
14864
|
-
values: { type: "string", description: "Comma-separated structured snippet values" },
|
|
14899
|
+
values: { type: "string", description: "Comma-separated structured snippet values (3-10, max 25 characters each)" },
|
|
14865
14900
|
cta: { type: "string", description: "Call-to-action text" },
|
|
14866
14901
|
text: { type: "string", description: "Text asset content" }
|
|
14867
14902
|
},
|
|
@@ -14886,7 +14921,7 @@ var assetsUpdateCommand = defineCommand30({
|
|
|
14886
14921
|
var assetsCommand = defineCommand30({
|
|
14887
14922
|
meta: {
|
|
14888
14923
|
name: "assets",
|
|
14889
|
-
description: "Stage asset create/update + asset-link attach/detach (via --file). For a high-performance Search campaign add \u22654 sitelinks, \u22653 callouts
|
|
14924
|
+
description: "Stage asset create/update + asset-link attach/detach (via --file). For a high-performance Search campaign add \u22654 sitelinks, \u22653 callouts and \u22651 structured snippet per campaign. Images are not among them: Google refuses every image asset link, so image extensions have to be added in the Google Ads interface."
|
|
14890
14925
|
},
|
|
14891
14926
|
subCommands: {
|
|
14892
14927
|
create: fileCreateCommand(
|
|
@@ -14895,7 +14930,9 @@ var assetsCommand = defineCommand30({
|
|
|
14895
14930
|
"Stage an asset (text/image/sitelink/callout/structuredSnippet/\u2026)"
|
|
14896
14931
|
),
|
|
14897
14932
|
update: assetsUpdateCommand,
|
|
14898
|
-
attach: fileCreateCommand("attach", "google.assetLink.attach", "Attach an asset to a campaign/adGroup/customer"
|
|
14933
|
+
attach: fileCreateCommand("attach", "google.assetLink.attach", "Attach an asset to a campaign/adGroup/customer", [
|
|
14934
|
+
"Images are not attachable here at all \u2014 Google refuses every image field type through an asset link, at every level. Image extensions have to be added in the Google Ads interface; say so and carry on. Performance Max asset-group images are a different mechanism and do work (`asset-groups attach`)."
|
|
14935
|
+
]),
|
|
14899
14936
|
detach: statusCommand2("google.assetLink.detach", "asset link")
|
|
14900
14937
|
}
|
|
14901
14938
|
});
|
|
@@ -47638,6 +47675,20 @@ function parseBooleanFlag(raw, flagName) {
|
|
|
47638
47675
|
}
|
|
47639
47676
|
failValidation4(`${flagName} must be true or false.`);
|
|
47640
47677
|
}
|
|
47678
|
+
function validateRunAt(runAt) {
|
|
47679
|
+
if (!runAt.endsWith("Z")) {
|
|
47680
|
+
failValidation4("--run-at must be an ISO UTC timestamp ending in Z.");
|
|
47681
|
+
}
|
|
47682
|
+
const parsed = Date.parse(runAt);
|
|
47683
|
+
if (!Number.isFinite(parsed)) {
|
|
47684
|
+
failValidation4("--run-at must be a valid ISO timestamp.");
|
|
47685
|
+
}
|
|
47686
|
+
if (parsed <= Date.now()) {
|
|
47687
|
+
failValidation4(
|
|
47688
|
+
`--run-at ${runAt} has already passed. A one-off task only runs at a time still to come \u2014 pass a later ISO UTC timestamp, and check which timezone you converted from if you worked it out from a local time.`
|
|
47689
|
+
);
|
|
47690
|
+
}
|
|
47691
|
+
}
|
|
47641
47692
|
function buildScheduleBody(args, options) {
|
|
47642
47693
|
const cron = args.cron;
|
|
47643
47694
|
const runAt = args["run-at"];
|
|
@@ -47646,12 +47697,7 @@ function buildScheduleBody(args, options) {
|
|
|
47646
47697
|
failValidation4("--cron and --run-at are mutually exclusive.");
|
|
47647
47698
|
}
|
|
47648
47699
|
if (runAt) {
|
|
47649
|
-
|
|
47650
|
-
failValidation4("--run-at must be an ISO UTC timestamp ending in Z.");
|
|
47651
|
-
}
|
|
47652
|
-
if (!Number.isFinite(Date.parse(runAt))) {
|
|
47653
|
-
failValidation4("--run-at must be a valid ISO timestamp.");
|
|
47654
|
-
}
|
|
47700
|
+
validateRunAt(runAt);
|
|
47655
47701
|
return { runAt, ...timezone ? { timezone } : {} };
|
|
47656
47702
|
}
|
|
47657
47703
|
if (cron) {
|
|
@@ -47676,7 +47722,7 @@ registerSchema({
|
|
|
47676
47722
|
cron: { type: "string", description: "5-field cron expression; mutually exclusive with --run-at", required: false },
|
|
47677
47723
|
"run-at": {
|
|
47678
47724
|
type: "string",
|
|
47679
|
-
description: "ISO UTC timestamp ending in Z
|
|
47725
|
+
description: "When this runs once, as an ISO UTC timestamp ending in Z. Must still be in the future \u2014 a time that has passed is refused. Mutually exclusive with --cron.",
|
|
47680
47726
|
required: false
|
|
47681
47727
|
},
|
|
47682
47728
|
timezone: { type: "string", description: "IANA timezone override; defaults to Company Timezone", required: false },
|
|
@@ -47710,7 +47756,7 @@ var createCommand3 = defineCommand181({
|
|
|
47710
47756
|
name: { type: "string", description: "Scheduled action name", required: false },
|
|
47711
47757
|
description: { type: "string", description: "Description", required: false },
|
|
47712
47758
|
cron: { type: "string", description: "5-field cron expression", required: false },
|
|
47713
|
-
"run-at": { type: "string", description: "ISO UTC timestamp ending in Z", required: false },
|
|
47759
|
+
"run-at": { type: "string", description: "ISO UTC timestamp ending in Z, still in the future", required: false },
|
|
47714
47760
|
timezone: { type: "string", description: "IANA timezone override", required: false },
|
|
47715
47761
|
disabled: { type: "boolean", description: "Create disabled", required: false, default: false },
|
|
47716
47762
|
mode: { type: "string", description: `One of: ${TASK_MODE_IDS.join(", ")}`, required: false },
|
|
@@ -48081,7 +48127,7 @@ registerSchema({
|
|
|
48081
48127
|
cron: { type: "string", description: "5-field cron expression; mutually exclusive with --run-at", required: false },
|
|
48082
48128
|
"run-at": {
|
|
48083
48129
|
type: "string",
|
|
48084
|
-
description: "ISO UTC timestamp ending in Z
|
|
48130
|
+
description: "New one-off run time, as an ISO UTC timestamp ending in Z. Must still be in the future \u2014 a time that has passed is refused. Mutually exclusive with --cron.",
|
|
48085
48131
|
required: false
|
|
48086
48132
|
},
|
|
48087
48133
|
timezone: { type: "string", description: "IANA timezone override", required: false },
|
|
@@ -48116,7 +48162,7 @@ var updateCommand3 = defineCommand187({
|
|
|
48116
48162
|
name: { type: "string", description: "New name", required: false },
|
|
48117
48163
|
description: { type: "string", description: "New description", required: false },
|
|
48118
48164
|
cron: { type: "string", description: "5-field cron expression", required: false },
|
|
48119
|
-
"run-at": { type: "string", description: "ISO UTC timestamp ending in Z", required: false },
|
|
48165
|
+
"run-at": { type: "string", description: "ISO UTC timestamp ending in Z, still in the future", required: false },
|
|
48120
48166
|
timezone: { type: "string", description: "IANA timezone override", required: false },
|
|
48121
48167
|
enabled: { type: "string", description: "true|false", required: false },
|
|
48122
48168
|
mode: { type: "string", description: `One of: ${TASK_MODE_IDS.join(", ")}`, required: false },
|