@koda-sl/baker-cli 0.347.0 → 0.349.0-dev.c05788ee3
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 +7 -5
- package/dist/{chunk-YBD3W4M7.js → chunk-CEFJ5YSG.js} +4 -4
- package/dist/chunk-CEFJ5YSG.js.map +1 -0
- package/dist/cli.js +190 -19
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-YBD3W4M7.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
validateCanvasDeep,
|
|
68
68
|
withoutCastName,
|
|
69
69
|
ytDlpBlockSignal
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-CEFJ5YSG.js";
|
|
71
71
|
import {
|
|
72
72
|
csvOrJson,
|
|
73
73
|
daysAgoIso,
|
|
@@ -1957,6 +1957,7 @@ var GOOGLE_ADS_LIMITS = {
|
|
|
1957
1957
|
businessNameMax: 25
|
|
1958
1958
|
}
|
|
1959
1959
|
};
|
|
1960
|
+
var GOOGLE_ADS_QUERY_MAX_PAGE_SIZE = 8192;
|
|
1960
1961
|
var KEYWORD_MATCH_TYPES = ["EXACT", "PHRASE", "BROAD"];
|
|
1961
1962
|
var ADVERTISING_CHANNEL_TYPES = [
|
|
1962
1963
|
"SEARCH",
|
|
@@ -9674,7 +9675,76 @@ function urlOptionsHints(report) {
|
|
|
9674
9675
|
return hints2;
|
|
9675
9676
|
}
|
|
9676
9677
|
|
|
9678
|
+
// src/commands/ads/google/during-literals.ts
|
|
9679
|
+
var GAQL_DURING_LITERALS = [
|
|
9680
|
+
"TODAY",
|
|
9681
|
+
"YESTERDAY",
|
|
9682
|
+
"LAST_7_DAYS",
|
|
9683
|
+
"LAST_14_DAYS",
|
|
9684
|
+
"LAST_30_DAYS",
|
|
9685
|
+
"LAST_BUSINESS_WEEK",
|
|
9686
|
+
"THIS_MONTH",
|
|
9687
|
+
"LAST_MONTH",
|
|
9688
|
+
"THIS_WEEK_SUN_TODAY",
|
|
9689
|
+
"THIS_WEEK_MON_TODAY",
|
|
9690
|
+
"LAST_WEEK_SUN_SAT",
|
|
9691
|
+
"LAST_WEEK_MON_SUN"
|
|
9692
|
+
];
|
|
9693
|
+
function isDuringLiteral(value) {
|
|
9694
|
+
return GAQL_DURING_LITERALS.includes(value.toUpperCase());
|
|
9695
|
+
}
|
|
9696
|
+
function isoDay(date) {
|
|
9697
|
+
return date.toISOString().slice(0, 10);
|
|
9698
|
+
}
|
|
9699
|
+
function utcDate(year, month, day) {
|
|
9700
|
+
return new Date(Date.UTC(year, month, day));
|
|
9701
|
+
}
|
|
9702
|
+
function monthsBefore(date, months) {
|
|
9703
|
+
const lastDayOfTarget = utcDate(date.getUTCFullYear(), date.getUTCMonth() - months + 1, 0).getUTCDate();
|
|
9704
|
+
return utcDate(date.getUTCFullYear(), date.getUTCMonth() - months, Math.min(date.getUTCDate(), lastDayOfTarget));
|
|
9705
|
+
}
|
|
9706
|
+
function equivalentBetween(literal, now) {
|
|
9707
|
+
const upper2 = literal.toUpperCase();
|
|
9708
|
+
const today = utcDate(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
9709
|
+
const yesterday = utcDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - 1);
|
|
9710
|
+
const range = (from, to) => `BETWEEN '${isoDay(from)}' AND '${isoDay(to)}'`;
|
|
9711
|
+
const year = today.getUTCFullYear();
|
|
9712
|
+
const quarterStart = Math.floor(today.getUTCMonth() / 3) * 3;
|
|
9713
|
+
const rolling = upper2.match(/^LAST_(\d+)_(DAYS|WEEKS|MONTHS|YEARS)$/);
|
|
9714
|
+
if (rolling) {
|
|
9715
|
+
const n = Number(rolling[1]);
|
|
9716
|
+
const unit = rolling[2];
|
|
9717
|
+
if (n < 1) return null;
|
|
9718
|
+
if (unit === "DAYS" || unit === "WEEKS") {
|
|
9719
|
+
const days = unit === "DAYS" ? n : n * 7;
|
|
9720
|
+
return range(utcDate(year, today.getUTCMonth(), today.getUTCDate() - days), yesterday);
|
|
9721
|
+
}
|
|
9722
|
+
const months = unit === "MONTHS" ? n : n * 12;
|
|
9723
|
+
return range(monthsBefore(today, months), yesterday);
|
|
9724
|
+
}
|
|
9725
|
+
switch (upper2) {
|
|
9726
|
+
case "THIS_YEAR":
|
|
9727
|
+
return range(utcDate(year, 0, 1), today);
|
|
9728
|
+
case "LAST_YEAR":
|
|
9729
|
+
return range(utcDate(year - 1, 0, 1), utcDate(year - 1, 11, 31));
|
|
9730
|
+
case "THIS_QUARTER":
|
|
9731
|
+
return range(utcDate(year, quarterStart, 1), today);
|
|
9732
|
+
case "LAST_QUARTER":
|
|
9733
|
+
return range(utcDate(year, quarterStart - 3, 1), utcDate(year, quarterStart, 0));
|
|
9734
|
+
default:
|
|
9735
|
+
return null;
|
|
9736
|
+
}
|
|
9737
|
+
}
|
|
9738
|
+
function duringLiteralExplanation(literal, between) {
|
|
9739
|
+
const accepted = GAQL_DURING_LITERALS.join(", ");
|
|
9740
|
+
const lead = between ? `Google does not accept ${literal} after DURING. The same window as explicit dates is ${between}.` : `Google does not accept ${literal} after DURING, and it names no fixed window \u2014 use BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD', or drop the date filter.`;
|
|
9741
|
+
return `${lead} DURING accepts only: ${accepted}. Any other window needs BETWEEN.`;
|
|
9742
|
+
}
|
|
9743
|
+
|
|
9677
9744
|
// src/commands/ads/google/changes-window.ts
|
|
9745
|
+
function monthlySpendTrendQuery(now = /* @__PURE__ */ new Date()) {
|
|
9746
|
+
return `SELECT campaign.name, segments.month, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date ${equivalentBetween("LAST_12_MONTHS", now)}`;
|
|
9747
|
+
}
|
|
9678
9748
|
function isChangeScope(value) {
|
|
9679
9749
|
return CHANGE_SCOPES.includes(value);
|
|
9680
9750
|
}
|
|
@@ -9728,7 +9798,7 @@ function lookbackTooLong(scope, days, max) {
|
|
|
9728
9798
|
message: `Google's account history API reaches back ${CHANGE_SCOPE_MAX_DAYS.summary} days at most; ${days} days is outside that window.`,
|
|
9729
9799
|
fix: {
|
|
9730
9800
|
action: "use_different_resource",
|
|
9731
|
-
explanation:
|
|
9801
|
+
explanation: `Do not abort \u2014 infer the change from performance instead. Query monthly metrics per campaign (${monthlySpendTrendQuery()}) to see which campaigns stopped or started spending. That shows what changed and when, though not who changed it. Older history exists only in the Google Ads web UI, which has no API \u2014 ask the user to check it if the 'who' matters.`
|
|
9732
9802
|
}
|
|
9733
9803
|
};
|
|
9734
9804
|
}
|
|
@@ -9778,6 +9848,27 @@ var CORRECTION_RULES = [
|
|
|
9778
9848
|
};
|
|
9779
9849
|
}
|
|
9780
9850
|
},
|
|
9851
|
+
// 0b. OPERATOR_FIELD_MISMATCH — the field refuses this operator, and Google names the
|
|
9852
|
+
// ones it takes. Kept ahead of the field-specific rules for the same reason as rule 0.
|
|
9853
|
+
// A repeated field (final_urls, labels, …) takes only CONTAINS ALL/ANY/NONE, so it has
|
|
9854
|
+
// no null test at all: read it unfiltered and filter the rows instead.
|
|
9855
|
+
{
|
|
9856
|
+
matchApiError: /OPERATOR_FIELD_MISMATCH|is not a valid operator to use with/i,
|
|
9857
|
+
fix: (ctx) => {
|
|
9858
|
+
const message = ctx.apiErrorMessage ?? "";
|
|
9859
|
+
const field = message.match(/valid operator to use with '([\w.]+)'/i)?.[1] ?? "this field";
|
|
9860
|
+
const operator = message.match(/([A-Z][A-Z_ =!<>]*?) is not a valid operator/)?.[1]?.trim();
|
|
9861
|
+
const valid = message.match(/Valid operators: (.+?)\.?(?:"|$)/i)?.[1]?.trim();
|
|
9862
|
+
const refused = operator ? `${operator} cannot be used on ${field}` : `${field} does not take that operator`;
|
|
9863
|
+
const allowed = valid ? `; its valid operators are ${valid}` : "";
|
|
9864
|
+
const repeated = valid?.split(",").every((op) => /^CONTAINS (ALL|ANY|NONE)$/i.test(op.trim())) ?? false;
|
|
9865
|
+
const guidance = repeated ? `. ${field} is a list field, so it cannot be tested for null or compared with =: to find rows that have it, drop that condition, keep ${field} in SELECT and filter the rows (rows without it omit the key); to match values, use ${field} CONTAINS ANY ('value')` : ". Rewrite that condition with one of them, or drop it and filter the rows";
|
|
9866
|
+
return {
|
|
9867
|
+
action: "change_operator",
|
|
9868
|
+
explanation: `${refused}${allowed}${guidance}`
|
|
9869
|
+
};
|
|
9870
|
+
}
|
|
9871
|
+
},
|
|
9781
9872
|
// 1. bare keyword.text → ad_group_criterion.keyword.text (leaves <resource>.keyword.text untouched)
|
|
9782
9873
|
{
|
|
9783
9874
|
matchQuery: /(?<![\w.])keyword\.text\b/,
|
|
@@ -9820,10 +9911,11 @@ var CORRECTION_RULES = [
|
|
|
9820
9911
|
};
|
|
9821
9912
|
}
|
|
9822
9913
|
},
|
|
9823
|
-
// 4. CONTAINS → LIKE
|
|
9914
|
+
// 4. CONTAINS → LIKE. Keyed on the query alone: Google's error envelope ("Request contains an
|
|
9915
|
+
// invalid argument … Valid operators: CONTAINS ANY") matches any loose error pattern, and this
|
|
9916
|
+
// advice only makes sense when the query actually wrote CONTAINS(…).
|
|
9824
9917
|
{
|
|
9825
9918
|
matchQuery: /CONTAINS\s*\(/i,
|
|
9826
|
-
matchApiError: /CONTAINS.*not.*supported|invalid.*operator.*CONTAINS/i,
|
|
9827
9919
|
fix: (ctx) => {
|
|
9828
9920
|
const match = ctx.originalQuery.match(/CONTAINS\s*\(\s*([^,]+),\s*'([^']+)'\s*\)/i);
|
|
9829
9921
|
if (match) {
|
|
@@ -10087,7 +10179,7 @@ var CORRECTION_RULES = [
|
|
|
10087
10179
|
matchApiError: /changeStatusError\W{0,4}START_DATE_TOO_OLD/i,
|
|
10088
10180
|
fix: () => ({
|
|
10089
10181
|
action: "use_different_resource",
|
|
10090
|
-
explanation:
|
|
10182
|
+
explanation: `Google's account history reaches back 90 days at most, and this query asks for more. Do not abort \u2014 infer the change from performance: ${monthlySpendTrendQuery()} shows which campaigns started or stopped spending and when, though not who changed them. Anything older with attribution exists only in the Google Ads web UI, which has no API.`
|
|
10091
10183
|
})
|
|
10092
10184
|
},
|
|
10093
10185
|
// 18c. change_status rejects a date range it cannot bound. Google raises this both for
|
|
@@ -11914,9 +12006,16 @@ import { resolve } from "path";
|
|
|
11914
12006
|
import { defineCommand as defineCommand28 } from "citty";
|
|
11915
12007
|
|
|
11916
12008
|
// src/commands/ads/google/presets.ts
|
|
11917
|
-
var
|
|
12009
|
+
var BETWEEN_RE = /^BETWEEN\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'\s+AND\s+'[0-9]{4}-[0-9]{2}-[0-9]{2}'$/i;
|
|
11918
12010
|
function isValidDateRange(value) {
|
|
11919
|
-
|
|
12011
|
+
const trimmed = value.trim();
|
|
12012
|
+
return isDuringLiteral(trimmed) || BETWEEN_RE.test(trimmed);
|
|
12013
|
+
}
|
|
12014
|
+
function dateRangeError(dateRange, now) {
|
|
12015
|
+
if (/^[A-Za-z0-9_]+$/.test(dateRange.trim())) {
|
|
12016
|
+
return `Invalid date range: "${dateRange}". ${duringLiteralExplanation(dateRange, equivalentBetween(dateRange, now))} Pass it as --date-range "BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'".`;
|
|
12017
|
+
}
|
|
12018
|
+
return `Invalid date range: "${dateRange}". Use a GAQL date literal (e.g. LAST_30_DAYS, TODAY) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.`;
|
|
11920
12019
|
}
|
|
11921
12020
|
var SERVING_CHAINS = {
|
|
11922
12021
|
keyword_view: ["campaign.status", "ad_group.status", "ad_group_criterion.status"],
|
|
@@ -12052,17 +12151,15 @@ function buildStatusScope(statusFields, includePaused) {
|
|
|
12052
12151
|
const predicate = includePaused ? "!= 'REMOVED'" : "= 'ENABLED'";
|
|
12053
12152
|
return statusFields.map((field) => ` AND ${field} ${predicate}`).join("");
|
|
12054
12153
|
}
|
|
12055
|
-
function expandPreset(preset, params) {
|
|
12154
|
+
function expandPreset(preset, params, now = /* @__PURE__ */ new Date()) {
|
|
12056
12155
|
const dateRange = params.dateRange ?? preset.defaultDateRange;
|
|
12057
12156
|
const limit = params.limit ?? preset.defaultLimit;
|
|
12058
|
-
if (!isValidDateRange(dateRange)) {
|
|
12059
|
-
return {
|
|
12060
|
-
query: "",
|
|
12061
|
-
dateRangeError: `Invalid date range: "${dateRange}". Use a GAQL date literal (e.g. LAST_30_DAYS, TODAY) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.`
|
|
12062
|
-
};
|
|
12157
|
+
if (preset.gaqlTemplate.includes("{dateRange}") && !isValidDateRange(dateRange)) {
|
|
12158
|
+
return { query: "", dateRangeError: dateRangeError(dateRange, now) };
|
|
12063
12159
|
}
|
|
12064
12160
|
const statusScope = buildStatusScope(preset.statusFields, params.includePaused ?? false);
|
|
12065
|
-
const
|
|
12161
|
+
const range = dateRange.trim();
|
|
12162
|
+
const query = preset.gaqlTemplate.replace(/DURING\s+\{dateRange\}/g, BETWEEN_RE.test(range) ? range : `DURING ${range}`).replace(/\{dateRange\}/g, range).replace(/\{limit\}/g, String(limit)).replace(/\{statusScope\}/g, statusScope);
|
|
12066
12163
|
return { query };
|
|
12067
12164
|
}
|
|
12068
12165
|
|
|
@@ -12221,6 +12318,49 @@ function applyAutoFixes(query, limit) {
|
|
|
12221
12318
|
}
|
|
12222
12319
|
return { query: corrected, warnings };
|
|
12223
12320
|
}
|
|
12321
|
+
var DURING_CLAUSE = /(?<=[a-z_]\.[a-z_.]+\s+)DURING\s+([A-Za-z0-9_]+)/gi;
|
|
12322
|
+
function rejectUnsupportedDuring(query, customerId) {
|
|
12323
|
+
const unsupported = [...query.matchAll(DURING_CLAUSE)].map((match) => match[1] ?? "").filter((literal2) => !isDuringLiteral(literal2));
|
|
12324
|
+
const literal = unsupported[0];
|
|
12325
|
+
if (literal === void 0) return null;
|
|
12326
|
+
const now = /* @__PURE__ */ new Date();
|
|
12327
|
+
const between = equivalentBetween(literal, now);
|
|
12328
|
+
const fixed = unsupported.every((l) => equivalentBetween(l, now) !== null) ? query.replace(
|
|
12329
|
+
DURING_CLAUSE,
|
|
12330
|
+
(clause, l) => isDuringLiteral(l) ? clause : equivalentBetween(l, now) ?? clause
|
|
12331
|
+
) : void 0;
|
|
12332
|
+
return {
|
|
12333
|
+
ok: false,
|
|
12334
|
+
error: {
|
|
12335
|
+
code: "INVALID_DATE_RANGE",
|
|
12336
|
+
message: `DURING ${literal} is not a Google Ads date range, so Google would refuse this query.`,
|
|
12337
|
+
fix: {
|
|
12338
|
+
action: "narrow_date_range",
|
|
12339
|
+
...fixed ? { correctedCommand: buildCommand2(fixed, customerId) } : {},
|
|
12340
|
+
explanation: duringLiteralExplanation(literal, between)
|
|
12341
|
+
},
|
|
12342
|
+
retryable: false
|
|
12343
|
+
}
|
|
12344
|
+
};
|
|
12345
|
+
}
|
|
12346
|
+
function rejectOversizedLimit(query, customerId) {
|
|
12347
|
+
const match = query.match(/\bLIMIT\s+(\d+)\s*$/i);
|
|
12348
|
+
if (!match || Number(match[1]) <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return null;
|
|
12349
|
+
const fixed = query.replace(/\bLIMIT\s+\d+\s*$/i, `LIMIT ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}`);
|
|
12350
|
+
return {
|
|
12351
|
+
ok: false,
|
|
12352
|
+
error: {
|
|
12353
|
+
code: "VALIDATION_ERROR",
|
|
12354
|
+
message: `LIMIT ${match[1]} is more rows than one read can return (${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}).`,
|
|
12355
|
+
fix: {
|
|
12356
|
+
action: "retry_with_flag",
|
|
12357
|
+
correctedCommand: buildCommand2(fixed, customerId),
|
|
12358
|
+
explanation: `One query returns at most ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} rows. For more, split the read into narrower queries \u2014 a shorter BETWEEN range each, or one campaign each \u2014 write each with --out, and combine the files.`
|
|
12359
|
+
},
|
|
12360
|
+
retryable: false
|
|
12361
|
+
}
|
|
12362
|
+
};
|
|
12363
|
+
}
|
|
12224
12364
|
function validatePreflight(query, customerId, limit) {
|
|
12225
12365
|
const warnings = [];
|
|
12226
12366
|
const corrected = query;
|
|
@@ -12292,6 +12432,10 @@ function validatePreflight(query, customerId, limit) {
|
|
|
12292
12432
|
}
|
|
12293
12433
|
};
|
|
12294
12434
|
}
|
|
12435
|
+
const duringRejection = rejectUnsupportedDuring(corrected, customerId);
|
|
12436
|
+
if (duringRejection) return { valid: false, warnings: [], error: duringRejection };
|
|
12437
|
+
const limitRejection = rejectOversizedLimit(corrected, customerId);
|
|
12438
|
+
if (limitRejection) return { valid: false, warnings: [], error: limitRejection };
|
|
12295
12439
|
if (/\bsegments\.conversion_action_name\b/i.test(corrected) && /\bmetrics\.cost_micros\b/i.test(corrected)) {
|
|
12296
12440
|
return {
|
|
12297
12441
|
valid: false,
|
|
@@ -12500,7 +12644,7 @@ registerSchema({
|
|
|
12500
12644
|
},
|
|
12501
12645
|
"date-range": {
|
|
12502
12646
|
type: "string",
|
|
12503
|
-
description: "Date range for presets (LAST_7_DAYS, LAST_30_DAYS,
|
|
12647
|
+
description: "Date range for presets: a Google date literal (TODAY, YESTERDAY, LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH \u2026) or BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'. Google has no LAST_90_DAYS \u2014 use BETWEEN for any other window.",
|
|
12504
12648
|
required: false
|
|
12505
12649
|
},
|
|
12506
12650
|
"include-paused": {
|
|
@@ -12510,7 +12654,7 @@ registerSchema({
|
|
|
12510
12654
|
},
|
|
12511
12655
|
limit: {
|
|
12512
12656
|
type: "number",
|
|
12513
|
-
description: "Max rows in ONE page (default 200), AND the LIMIT added to a query that has none \u2014 so it caps the whole read with --all too. Values up to
|
|
12657
|
+
description: "Max rows in ONE page (default 200), AND the LIMIT added to a query that has none \u2014 so it caps the whole read with --all too. Values up to 8192 are accepted \u2014 the most one read can return. Any read that fills this cap comes back flagged TRUNCATED_RESULTS \u2014 never count or total from one.",
|
|
12514
12658
|
required: false,
|
|
12515
12659
|
default: 200
|
|
12516
12660
|
},
|
|
@@ -12518,7 +12662,7 @@ registerSchema({
|
|
|
12518
12662
|
cursor: { type: "string", description: "Pagination cursor from previous response", required: false },
|
|
12519
12663
|
all: {
|
|
12520
12664
|
type: "boolean",
|
|
12521
|
-
description: "Follow every page until the data set is exhausted (use with --out for large datasets). Needed for a read you may count or total, but not sufficient: it exhausts the pages, not the statement's LIMIT, so pair it with a
|
|
12665
|
+
description: "Follow every page until the data set is exhausted (use with --out for large datasets). Needed for a read you may count or total, but not sufficient: it exhausts the pages, not the statement's LIMIT, so pair it with a LIMIT in the statement above the row count you expect (at most 8192 \u2014 split bigger reads into narrower queries) and check the read came back without TRUNCATED_RESULTS.",
|
|
12522
12666
|
required: false
|
|
12523
12667
|
},
|
|
12524
12668
|
out: {
|
|
@@ -12605,7 +12749,8 @@ function completenessWarnings(read) {
|
|
|
12605
12749
|
const filledTheCap = cap3 !== void 0 && cap3 > 0 && read.rowCount >= cap3;
|
|
12606
12750
|
if (!read.hasMore && !filledTheCap) return [];
|
|
12607
12751
|
const cause = read.hasMore ? `More rows are waiting: the account returned a next-page cursor this run did not follow.` : `The ${read.rowCount} rows returned exactly fill the row cap in force (${cap3}).`;
|
|
12608
|
-
const
|
|
12752
|
+
const atPageCap = cap3 !== void 0 && cap3 >= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE;
|
|
12753
|
+
const fix = atPageCap ? `${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} rows is the most one read can return \u2014 split it into narrower queries (a shorter BETWEEN range each, or one campaign each), write each with --out, and combine the files.` : read.all ? `Every page was followed, so the cap is the LIMIT in the statement itself \u2014 raise it above ${cap3}, or drop it and pass a larger --limit (up to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}), then re-run.` : `Re-run with --all --out <file> to page through everything, or raise --limit above ${cap3} (up to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}).`;
|
|
12609
12754
|
return [
|
|
12610
12755
|
{
|
|
12611
12756
|
code: "TRUNCATED_RESULTS",
|
|
@@ -12939,9 +13084,35 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
|
|
|
12939
13084
|
function shouldReadCache(args5) {
|
|
12940
13085
|
return !args5["no-cache"] && !args5.out;
|
|
12941
13086
|
}
|
|
13087
|
+
var DEFAULT_QUERY_LIMIT = 200;
|
|
13088
|
+
function parseQueryLimit(raw) {
|
|
13089
|
+
if (raw === void 0 || raw === null || raw === "") return { ok: true, limit: DEFAULT_QUERY_LIMIT };
|
|
13090
|
+
const limit = Number(raw);
|
|
13091
|
+
if (Number.isInteger(limit) && limit >= 1 && limit <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit };
|
|
13092
|
+
return {
|
|
13093
|
+
ok: false,
|
|
13094
|
+
error: {
|
|
13095
|
+
ok: false,
|
|
13096
|
+
error: {
|
|
13097
|
+
code: "VALIDATION_ERROR",
|
|
13098
|
+
message: `--limit must be a whole number from 1 to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}, got '${String(raw)}'.`,
|
|
13099
|
+
fix: {
|
|
13100
|
+
action: "retry_with_flag",
|
|
13101
|
+
explanation: `One read returns at most ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE} rows. For more, split the read into narrower queries (a shorter BETWEEN range each, or one campaign each), write each with --out, and combine the files.`
|
|
13102
|
+
},
|
|
13103
|
+
retryable: false
|
|
13104
|
+
}
|
|
13105
|
+
}
|
|
13106
|
+
};
|
|
13107
|
+
}
|
|
12942
13108
|
async function validateArgs(args5) {
|
|
13109
|
+
const parsedLimit = parseQueryLimit(args5.limit);
|
|
13110
|
+
if (!parsedLimit.ok) {
|
|
13111
|
+
writeAdsJson(parsedLimit.error);
|
|
13112
|
+
process.exit(1);
|
|
13113
|
+
}
|
|
13114
|
+
const { limit } = parsedLimit;
|
|
12943
13115
|
const customerId = await resolveCustomerId(args5);
|
|
12944
|
-
const limit = args5.limit ? Number(args5.limit) : 200;
|
|
12945
13116
|
const gaql = resolveGaql(args5, limit);
|
|
12946
13117
|
if (!gaql) {
|
|
12947
13118
|
process.exit(1);
|