@koda-sl/baker-cli 0.347.0 → 0.348.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 +6 -4
- package/dist/cli.js +165 -16
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -304,7 +304,7 @@ Read it before interpreting any null. Field coverage is folded page by page on t
|
|
|
304
304
|
}
|
|
305
305
|
```
|
|
306
306
|
|
|
307
|
-
Never total or count from a response carrying it — re-run with `--all --limit
|
|
307
|
+
Never total or count from a response carrying it — re-run with `--all --limit 8192 --out data.csv` (one read returns at most 8,192 rows; split a bigger data set into narrower queries — a shorter date range or one campaign each) and check the re-run came back without the warning. A read that returns one row by construction — `FROM customer` with no segment selected, such as `--preset account-summary` — has no page to fill, so it never carries `TRUNCATED_RESULTS`, nor `CONVERSION_MIX`, since one account-wide row has no campaigns to total or compare. Warnings reach every output format, so no `--output` choice can hide one:
|
|
308
308
|
|
|
309
309
|
| `--output` | Where warnings land |
|
|
310
310
|
|------------|---------------------|
|
|
@@ -343,11 +343,11 @@ The same lines also go to stderr. A read with nothing to report prints none of i
|
|
|
343
343
|
|-----------------|--------------------------------------------------------------|
|
|
344
344
|
| `--customer-id` | Google Ads customer ID (10 digits, no dashes). Falls back to `BAKER_GOOGLE_ADS_CUSTOMER_ID` env var. |
|
|
345
345
|
| `--preset` | Named query template (see presets below) |
|
|
346
|
-
| `--date-range` | Override preset date range (LAST_7_DAYS, LAST_30_DAYS, etc.) |
|
|
346
|
+
| `--date-range` | Override preset date range: a Google literal (LAST_7_DAYS, LAST_30_DAYS, LAST_MONTH, etc.) or `BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'` — there is no LAST_90_DAYS |
|
|
347
347
|
| `--include-paused` | Include paused entities in preset results. Off by default: presets return only actually-serving entities (enabled campaign **and** ad group **and** ad). Google Ads has no single serving flag, so the whole chain must be `ENABLED` for an entity to serve. |
|
|
348
|
-
| `--limit` | Max rows per page (default 200)
|
|
348
|
+
| `--limit` | Max rows per page (default 200, at most 8,192) |
|
|
349
349
|
| `--cursor` | Pagination cursor from previous response |
|
|
350
|
-
| `--all` | Auto-paginate all results. Exhausts the pages, not the statement's `LIMIT` — pair it with a
|
|
350
|
+
| `--all` | Auto-paginate all results. Exhausts the pages, not the statement's `LIMIT` — pair it with a `LIMIT` in the statement above the expected row count (at most 8,192 — split bigger reads into narrower queries), and a read that still fills its cap is flagged `TRUNCATED_RESULTS` either way |
|
|
351
351
|
| `--out` | Write data to file (format from extension: .csv, .jsonl, .json) |
|
|
352
352
|
| `--append` | Append to existing file (skip CSV headers) |
|
|
353
353
|
| `--output` | Output format: `json` \| `csv` \| `jsonl` \| `md` |
|
|
@@ -391,6 +391,8 @@ The CLI rejects or automatically corrects common GAQL mistakes before hitting th
|
|
|
391
391
|
| `shopping_performance_view.product_*` | Rewrites to `segments.product_*` | Field renamed |
|
|
392
392
|
| `campaign.status = 'ACTIVE'` | Rewrites to `= 'ENABLED'` | Enum corrected |
|
|
393
393
|
| `segments.conversion_action_name` with `metrics.cost_micros` | Rejects; split into separate conversion-action and spend queries | `INCOMPATIBLE_FIELDS` with `fix.action: "split_query"` |
|
|
394
|
+
| `DURING` with a literal Google does not accept (`LAST_90_DAYS`, `LAST_12_MONTHS`, `THIS_YEAR` …) | Rejects before any request; `DURING` takes only `TODAY`, `YESTERDAY`, `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`, `LAST_BUSINESS_WEEK`, `THIS_MONTH`, `LAST_MONTH`, `THIS_WEEK_SUN_TODAY`, `THIS_WEEK_MON_TODAY`, `LAST_WEEK_SUN_SAT`, `LAST_WEEK_MON_SUN` | `INVALID_DATE_RANGE`, with `fix.correctedCommand` spelling the same window as `BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'` |
|
|
395
|
+
| `--limit` outside 1–8,192, or a statement `LIMIT` above 8,192 | Rejects before any request — one read returns at most 8,192 rows | `VALIDATION_ERROR` naming the cap and how to split a bigger read; a statement `LIMIT` also gets `fix.correctedCommand` at `LIMIT 8192` |
|
|
394
396
|
| `FROM ad_group_criterion` without `negative` field | Warns | `"ad_group_criterion returns BOTH positive and negative keywords. Use --preset positive-keywords or negative-keywords."` |
|
|
395
397
|
|
|
396
398
|
Auto-fixed queries proceed normally. Warnings appear in the response:
|
package/dist/cli.js
CHANGED
|
@@ -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
|
}
|
|
@@ -10087,7 +10157,7 @@ var CORRECTION_RULES = [
|
|
|
10087
10157
|
matchApiError: /changeStatusError\W{0,4}START_DATE_TOO_OLD/i,
|
|
10088
10158
|
fix: () => ({
|
|
10089
10159
|
action: "use_different_resource",
|
|
10090
|
-
explanation:
|
|
10160
|
+
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
10161
|
})
|
|
10092
10162
|
},
|
|
10093
10163
|
// 18c. change_status rejects a date range it cannot bound. Google raises this both for
|
|
@@ -11914,9 +11984,16 @@ import { resolve } from "path";
|
|
|
11914
11984
|
import { defineCommand as defineCommand28 } from "citty";
|
|
11915
11985
|
|
|
11916
11986
|
// src/commands/ads/google/presets.ts
|
|
11917
|
-
var
|
|
11987
|
+
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
11988
|
function isValidDateRange(value) {
|
|
11919
|
-
|
|
11989
|
+
const trimmed = value.trim();
|
|
11990
|
+
return isDuringLiteral(trimmed) || BETWEEN_RE.test(trimmed);
|
|
11991
|
+
}
|
|
11992
|
+
function dateRangeError(dateRange, now) {
|
|
11993
|
+
if (/^[A-Za-z0-9_]+$/.test(dateRange.trim())) {
|
|
11994
|
+
return `Invalid date range: "${dateRange}". ${duringLiteralExplanation(dateRange, equivalentBetween(dateRange, now))} Pass it as --date-range "BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'".`;
|
|
11995
|
+
}
|
|
11996
|
+
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
11997
|
}
|
|
11921
11998
|
var SERVING_CHAINS = {
|
|
11922
11999
|
keyword_view: ["campaign.status", "ad_group.status", "ad_group_criterion.status"],
|
|
@@ -12052,17 +12129,15 @@ function buildStatusScope(statusFields, includePaused) {
|
|
|
12052
12129
|
const predicate = includePaused ? "!= 'REMOVED'" : "= 'ENABLED'";
|
|
12053
12130
|
return statusFields.map((field) => ` AND ${field} ${predicate}`).join("");
|
|
12054
12131
|
}
|
|
12055
|
-
function expandPreset(preset, params) {
|
|
12132
|
+
function expandPreset(preset, params, now = /* @__PURE__ */ new Date()) {
|
|
12056
12133
|
const dateRange = params.dateRange ?? preset.defaultDateRange;
|
|
12057
12134
|
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
|
-
};
|
|
12135
|
+
if (preset.gaqlTemplate.includes("{dateRange}") && !isValidDateRange(dateRange)) {
|
|
12136
|
+
return { query: "", dateRangeError: dateRangeError(dateRange, now) };
|
|
12063
12137
|
}
|
|
12064
12138
|
const statusScope = buildStatusScope(preset.statusFields, params.includePaused ?? false);
|
|
12065
|
-
const
|
|
12139
|
+
const range = dateRange.trim();
|
|
12140
|
+
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
12141
|
return { query };
|
|
12067
12142
|
}
|
|
12068
12143
|
|
|
@@ -12221,6 +12296,49 @@ function applyAutoFixes(query, limit) {
|
|
|
12221
12296
|
}
|
|
12222
12297
|
return { query: corrected, warnings };
|
|
12223
12298
|
}
|
|
12299
|
+
var DURING_CLAUSE = /(?<=[a-z_]\.[a-z_.]+\s+)DURING\s+([A-Za-z0-9_]+)/gi;
|
|
12300
|
+
function rejectUnsupportedDuring(query, customerId) {
|
|
12301
|
+
const unsupported = [...query.matchAll(DURING_CLAUSE)].map((match) => match[1] ?? "").filter((literal2) => !isDuringLiteral(literal2));
|
|
12302
|
+
const literal = unsupported[0];
|
|
12303
|
+
if (literal === void 0) return null;
|
|
12304
|
+
const now = /* @__PURE__ */ new Date();
|
|
12305
|
+
const between = equivalentBetween(literal, now);
|
|
12306
|
+
const fixed = unsupported.every((l) => equivalentBetween(l, now) !== null) ? query.replace(
|
|
12307
|
+
DURING_CLAUSE,
|
|
12308
|
+
(clause, l) => isDuringLiteral(l) ? clause : equivalentBetween(l, now) ?? clause
|
|
12309
|
+
) : void 0;
|
|
12310
|
+
return {
|
|
12311
|
+
ok: false,
|
|
12312
|
+
error: {
|
|
12313
|
+
code: "INVALID_DATE_RANGE",
|
|
12314
|
+
message: `DURING ${literal} is not a Google Ads date range, so Google would refuse this query.`,
|
|
12315
|
+
fix: {
|
|
12316
|
+
action: "narrow_date_range",
|
|
12317
|
+
...fixed ? { correctedCommand: buildCommand2(fixed, customerId) } : {},
|
|
12318
|
+
explanation: duringLiteralExplanation(literal, between)
|
|
12319
|
+
},
|
|
12320
|
+
retryable: false
|
|
12321
|
+
}
|
|
12322
|
+
};
|
|
12323
|
+
}
|
|
12324
|
+
function rejectOversizedLimit(query, customerId) {
|
|
12325
|
+
const match = query.match(/\bLIMIT\s+(\d+)\s*$/i);
|
|
12326
|
+
if (!match || Number(match[1]) <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return null;
|
|
12327
|
+
const fixed = query.replace(/\bLIMIT\s+\d+\s*$/i, `LIMIT ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}`);
|
|
12328
|
+
return {
|
|
12329
|
+
ok: false,
|
|
12330
|
+
error: {
|
|
12331
|
+
code: "VALIDATION_ERROR",
|
|
12332
|
+
message: `LIMIT ${match[1]} is more rows than one read can return (${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}).`,
|
|
12333
|
+
fix: {
|
|
12334
|
+
action: "retry_with_flag",
|
|
12335
|
+
correctedCommand: buildCommand2(fixed, customerId),
|
|
12336
|
+
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.`
|
|
12337
|
+
},
|
|
12338
|
+
retryable: false
|
|
12339
|
+
}
|
|
12340
|
+
};
|
|
12341
|
+
}
|
|
12224
12342
|
function validatePreflight(query, customerId, limit) {
|
|
12225
12343
|
const warnings = [];
|
|
12226
12344
|
const corrected = query;
|
|
@@ -12292,6 +12410,10 @@ function validatePreflight(query, customerId, limit) {
|
|
|
12292
12410
|
}
|
|
12293
12411
|
};
|
|
12294
12412
|
}
|
|
12413
|
+
const duringRejection = rejectUnsupportedDuring(corrected, customerId);
|
|
12414
|
+
if (duringRejection) return { valid: false, warnings: [], error: duringRejection };
|
|
12415
|
+
const limitRejection = rejectOversizedLimit(corrected, customerId);
|
|
12416
|
+
if (limitRejection) return { valid: false, warnings: [], error: limitRejection };
|
|
12295
12417
|
if (/\bsegments\.conversion_action_name\b/i.test(corrected) && /\bmetrics\.cost_micros\b/i.test(corrected)) {
|
|
12296
12418
|
return {
|
|
12297
12419
|
valid: false,
|
|
@@ -12500,7 +12622,7 @@ registerSchema({
|
|
|
12500
12622
|
},
|
|
12501
12623
|
"date-range": {
|
|
12502
12624
|
type: "string",
|
|
12503
|
-
description: "Date range for presets (LAST_7_DAYS, LAST_30_DAYS,
|
|
12625
|
+
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
12626
|
required: false
|
|
12505
12627
|
},
|
|
12506
12628
|
"include-paused": {
|
|
@@ -12510,7 +12632,7 @@ registerSchema({
|
|
|
12510
12632
|
},
|
|
12511
12633
|
limit: {
|
|
12512
12634
|
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
|
|
12635
|
+
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
12636
|
required: false,
|
|
12515
12637
|
default: 200
|
|
12516
12638
|
},
|
|
@@ -12518,7 +12640,7 @@ registerSchema({
|
|
|
12518
12640
|
cursor: { type: "string", description: "Pagination cursor from previous response", required: false },
|
|
12519
12641
|
all: {
|
|
12520
12642
|
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
|
|
12643
|
+
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
12644
|
required: false
|
|
12523
12645
|
},
|
|
12524
12646
|
out: {
|
|
@@ -12605,7 +12727,8 @@ function completenessWarnings(read) {
|
|
|
12605
12727
|
const filledTheCap = cap3 !== void 0 && cap3 > 0 && read.rowCount >= cap3;
|
|
12606
12728
|
if (!read.hasMore && !filledTheCap) return [];
|
|
12607
12729
|
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
|
|
12730
|
+
const atPageCap = cap3 !== void 0 && cap3 >= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE;
|
|
12731
|
+
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
12732
|
return [
|
|
12610
12733
|
{
|
|
12611
12734
|
code: "TRUNCATED_RESULTS",
|
|
@@ -12939,9 +13062,35 @@ async function executeQuery(finalQuery, args5, customerId, limit, warnings, useC
|
|
|
12939
13062
|
function shouldReadCache(args5) {
|
|
12940
13063
|
return !args5["no-cache"] && !args5.out;
|
|
12941
13064
|
}
|
|
13065
|
+
var DEFAULT_QUERY_LIMIT = 200;
|
|
13066
|
+
function parseQueryLimit(raw) {
|
|
13067
|
+
if (raw === void 0 || raw === null || raw === "") return { ok: true, limit: DEFAULT_QUERY_LIMIT };
|
|
13068
|
+
const limit = Number(raw);
|
|
13069
|
+
if (Number.isInteger(limit) && limit >= 1 && limit <= GOOGLE_ADS_QUERY_MAX_PAGE_SIZE) return { ok: true, limit };
|
|
13070
|
+
return {
|
|
13071
|
+
ok: false,
|
|
13072
|
+
error: {
|
|
13073
|
+
ok: false,
|
|
13074
|
+
error: {
|
|
13075
|
+
code: "VALIDATION_ERROR",
|
|
13076
|
+
message: `--limit must be a whole number from 1 to ${GOOGLE_ADS_QUERY_MAX_PAGE_SIZE}, got '${String(raw)}'.`,
|
|
13077
|
+
fix: {
|
|
13078
|
+
action: "retry_with_flag",
|
|
13079
|
+
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.`
|
|
13080
|
+
},
|
|
13081
|
+
retryable: false
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
13084
|
+
};
|
|
13085
|
+
}
|
|
12942
13086
|
async function validateArgs(args5) {
|
|
13087
|
+
const parsedLimit = parseQueryLimit(args5.limit);
|
|
13088
|
+
if (!parsedLimit.ok) {
|
|
13089
|
+
writeAdsJson(parsedLimit.error);
|
|
13090
|
+
process.exit(1);
|
|
13091
|
+
}
|
|
13092
|
+
const { limit } = parsedLimit;
|
|
12943
13093
|
const customerId = await resolveCustomerId(args5);
|
|
12944
|
-
const limit = args5.limit ? Number(args5.limit) : 200;
|
|
12945
13094
|
const gaql = resolveGaql(args5, limit);
|
|
12946
13095
|
if (!gaql) {
|
|
12947
13096
|
process.exit(1);
|