siluzan-tso-cli 1.1.34 → 1.1.35-beta.2
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 +2 -1
- package/dist/index.js +482 -323
- package/dist/skill/_meta.json +2 -2
- package/dist/skill/assets/market-analysis-rules.md +7 -7
- package/dist/skill/references/accounts/accounts.md +3 -3
- package/dist/skill/references/accounts/finance.md +1 -1
- package/dist/skill/references/accounts/open-account-by-media.md +1 -1
- package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
- package/dist/skill/references/analytics/rag.md +1 -1
- package/dist/skill/references/analytics/reporting.md +5 -5
- package/dist/skill/references/core/playbooks.md +1 -1
- package/dist/skill/references/core/setup.md +5 -5
- package/dist/skill/references/misc/tso-home.md +2 -2
- package/dist/skill/references/report-templates/google-ads-diagnosis.md +14 -1
- package/dist/skill/report-templates/google-ads-diagnosis.md +14 -1
- package/dist/skill/scripts/install.ps1 +3 -3
- package/dist/skill/scripts/install.sh +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3829,7 +3829,7 @@ var DEFAULT_API_BASE;
|
|
|
3829
3829
|
var init_defaults = __esm({
|
|
3830
3830
|
"src/config/defaults.ts"() {
|
|
3831
3831
|
"use strict";
|
|
3832
|
-
DEFAULT_API_BASE = "https://tso-api.siluzan.com";
|
|
3832
|
+
DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
|
|
3833
3833
|
}
|
|
3834
3834
|
});
|
|
3835
3835
|
|
|
@@ -5754,6 +5754,150 @@ var init_sections = __esm({
|
|
|
5754
5754
|
}
|
|
5755
5755
|
});
|
|
5756
5756
|
|
|
5757
|
+
// src/utils/http-retry.ts
|
|
5758
|
+
function classifyError(err) {
|
|
5759
|
+
const status = parseHttpStatusFromError(err);
|
|
5760
|
+
if (status === 401) return { class: "abort", status };
|
|
5761
|
+
if (status === void 0) {
|
|
5762
|
+
return { class: "retryable" };
|
|
5763
|
+
}
|
|
5764
|
+
if (status === 408 || status === 429) return { class: "retryable", status };
|
|
5765
|
+
if (status >= 500 && status <= 599) return { class: "retryable", status };
|
|
5766
|
+
if (status >= 400 && status <= 499) return { class: "permanent", status };
|
|
5767
|
+
return { class: "permanent", status };
|
|
5768
|
+
}
|
|
5769
|
+
function computeBackoffMs(attempt, policy) {
|
|
5770
|
+
const exp = policy.baseMs * 2 ** Math.max(0, attempt - 1);
|
|
5771
|
+
const capped = Math.min(exp, policy.maxBackoffMs);
|
|
5772
|
+
const jitter = Math.floor(Math.random() * policy.baseMs);
|
|
5773
|
+
return capped + jitter;
|
|
5774
|
+
}
|
|
5775
|
+
function sleep3(ms, signal) {
|
|
5776
|
+
return new Promise((resolve15, reject) => {
|
|
5777
|
+
if (signal?.aborted) {
|
|
5778
|
+
reject(new Error("retry sleep aborted"));
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
const timer = setTimeout(() => {
|
|
5782
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5783
|
+
resolve15();
|
|
5784
|
+
}, ms);
|
|
5785
|
+
const onAbort = () => {
|
|
5786
|
+
clearTimeout(timer);
|
|
5787
|
+
reject(new Error("retry sleep aborted"));
|
|
5788
|
+
};
|
|
5789
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5790
|
+
});
|
|
5791
|
+
}
|
|
5792
|
+
async function withTaskTimeout(factory, timeoutMs, label) {
|
|
5793
|
+
if (!timeoutMs || timeoutMs <= 0) return factory();
|
|
5794
|
+
let timer;
|
|
5795
|
+
try {
|
|
5796
|
+
return await Promise.race([
|
|
5797
|
+
factory(),
|
|
5798
|
+
new Promise((_, reject) => {
|
|
5799
|
+
timer = setTimeout(() => {
|
|
5800
|
+
reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${label}`));
|
|
5801
|
+
}, timeoutMs);
|
|
5802
|
+
})
|
|
5803
|
+
]);
|
|
5804
|
+
} finally {
|
|
5805
|
+
if (timer) clearTimeout(timer);
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
async function fetchWithRetry(factory, options) {
|
|
5809
|
+
const policy = { ...DEFAULT_RETRY_POLICY, ...options.policy ?? {} };
|
|
5810
|
+
let lastErr;
|
|
5811
|
+
let lastStatus;
|
|
5812
|
+
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
5813
|
+
if (options.signal?.aborted) {
|
|
5814
|
+
throw new AbortRunError({
|
|
5815
|
+
message: `\u4EFB\u52A1\u88AB\u5916\u90E8\u4FE1\u53F7\u4E2D\u6B62\uFF1A${options.label}`
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
try {
|
|
5819
|
+
return await withTaskTimeout(factory, policy.taskTimeoutMs, options.label);
|
|
5820
|
+
} catch (raw) {
|
|
5821
|
+
const err = raw instanceof Error ? raw : new Error(String(raw));
|
|
5822
|
+
const cls = classifyError(err);
|
|
5823
|
+
lastErr = err;
|
|
5824
|
+
lastStatus = cls.status;
|
|
5825
|
+
if (cls.class === "abort") {
|
|
5826
|
+
throw new AbortRunError({
|
|
5827
|
+
message: `Token \u5931\u6548\u6216\u81F4\u547D\u9519\u8BEF\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
5828
|
+
status: cls.status,
|
|
5829
|
+
cause: err
|
|
5830
|
+
});
|
|
5831
|
+
}
|
|
5832
|
+
if (cls.class === "permanent" || attempt >= policy.maxAttempts) {
|
|
5833
|
+
throw new HttpRetryError({
|
|
5834
|
+
message: cls.class === "permanent" ? `\u6C38\u4E45\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}` : `\u91CD\u8BD5 ${attempt} \u6B21\u4ECD\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
5835
|
+
class: cls.class === "permanent" ? "permanent" : "retryable",
|
|
5836
|
+
status: cls.status,
|
|
5837
|
+
attempts: attempt,
|
|
5838
|
+
cause: err
|
|
5839
|
+
});
|
|
5840
|
+
}
|
|
5841
|
+
const backoffMs = computeBackoffMs(attempt, policy);
|
|
5842
|
+
options.onRetry?.({ attempt, backoffMs, status: cls.status, error: err });
|
|
5843
|
+
try {
|
|
5844
|
+
await sleep3(backoffMs, options.signal);
|
|
5845
|
+
} catch {
|
|
5846
|
+
throw new AbortRunError({
|
|
5847
|
+
message: `\u91CD\u8BD5\u7B49\u5F85\u88AB\u4E2D\u6B62\uFF1A${options.label}`,
|
|
5848
|
+
cause: err
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
throw new HttpRetryError({
|
|
5854
|
+
message: `\u672A\u77E5\u91CD\u8BD5\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${lastErr?.message ?? "no error"}`,
|
|
5855
|
+
class: "retryable",
|
|
5856
|
+
status: lastStatus,
|
|
5857
|
+
attempts: policy.maxAttempts,
|
|
5858
|
+
cause: lastErr
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
var HttpRetryError, AbortRunError, DEFAULT_RETRY_POLICY;
|
|
5862
|
+
var init_http_retry = __esm({
|
|
5863
|
+
"src/utils/http-retry.ts"() {
|
|
5864
|
+
"use strict";
|
|
5865
|
+
init_dist();
|
|
5866
|
+
HttpRetryError = class extends Error {
|
|
5867
|
+
class;
|
|
5868
|
+
status;
|
|
5869
|
+
attempts;
|
|
5870
|
+
cause;
|
|
5871
|
+
constructor(opts) {
|
|
5872
|
+
super(opts.message);
|
|
5873
|
+
this.name = "HttpRetryError";
|
|
5874
|
+
this.class = opts.class;
|
|
5875
|
+
this.status = opts.status;
|
|
5876
|
+
this.attempts = opts.attempts;
|
|
5877
|
+
this.cause = opts.cause;
|
|
5878
|
+
}
|
|
5879
|
+
};
|
|
5880
|
+
AbortRunError = class extends HttpRetryError {
|
|
5881
|
+
constructor(opts) {
|
|
5882
|
+
super({
|
|
5883
|
+
message: opts.message,
|
|
5884
|
+
class: "abort",
|
|
5885
|
+
status: opts.status,
|
|
5886
|
+
attempts: 1,
|
|
5887
|
+
cause: opts.cause
|
|
5888
|
+
});
|
|
5889
|
+
this.name = "AbortRunError";
|
|
5890
|
+
}
|
|
5891
|
+
};
|
|
5892
|
+
DEFAULT_RETRY_POLICY = {
|
|
5893
|
+
maxAttempts: 3,
|
|
5894
|
+
baseMs: 1e3,
|
|
5895
|
+
maxBackoffMs: 3e4,
|
|
5896
|
+
taskTimeoutMs: 6e4
|
|
5897
|
+
};
|
|
5898
|
+
}
|
|
5899
|
+
});
|
|
5900
|
+
|
|
5757
5901
|
// src/commands/google-analysis/normalize-impression-shares.ts
|
|
5758
5902
|
function normalizeShareValue(value, competitiveMetrics, field) {
|
|
5759
5903
|
const cmVal = competitiveMetrics?.[field];
|
|
@@ -101399,6 +101543,28 @@ var init_translate_fields = __esm({
|
|
|
101399
101543
|
}
|
|
101400
101544
|
});
|
|
101401
101545
|
|
|
101546
|
+
// src/commands/google-analysis/daily-metrics-range.ts
|
|
101547
|
+
function pad2(n) {
|
|
101548
|
+
return String(n).padStart(2, "0");
|
|
101549
|
+
}
|
|
101550
|
+
function formatChinaOffsetDateTime(d) {
|
|
101551
|
+
const sh = new Date(d.getTime() + CHINA_OFFSET_MS);
|
|
101552
|
+
return `${sh.getUTCFullYear()}-${pad2(sh.getUTCMonth() + 1)}-${pad2(sh.getUTCDate())}T${pad2(sh.getUTCHours())}:${pad2(sh.getUTCMinutes())}:${pad2(sh.getUTCSeconds())}+08:00`;
|
|
101553
|
+
}
|
|
101554
|
+
function resolveDailyMetricsQueryTimes(startDate, endDate, now = /* @__PURE__ */ new Date()) {
|
|
101555
|
+
const startInstant = `${startDate}T00:00:00+08:00`;
|
|
101556
|
+
const endOfDay = /* @__PURE__ */ new Date(`${endDate}T23:59:59+08:00`);
|
|
101557
|
+
const endInstant = endOfDay.getTime() > now.getTime() ? formatChinaOffsetDateTime(now) : formatChinaOffsetDateTime(endOfDay);
|
|
101558
|
+
return { startInstant, endInstant };
|
|
101559
|
+
}
|
|
101560
|
+
var CHINA_OFFSET_MS;
|
|
101561
|
+
var init_daily_metrics_range = __esm({
|
|
101562
|
+
"src/commands/google-analysis/daily-metrics-range.ts"() {
|
|
101563
|
+
"use strict";
|
|
101564
|
+
CHINA_OFFSET_MS = 8 * 36e5;
|
|
101565
|
+
}
|
|
101566
|
+
});
|
|
101567
|
+
|
|
101402
101568
|
// src/commands/google-analysis/fetch.ts
|
|
101403
101569
|
function defaultDateRange3() {
|
|
101404
101570
|
const end = /* @__PURE__ */ new Date();
|
|
@@ -101432,9 +101598,19 @@ function buildSearchParams(def, start, end, extras) {
|
|
|
101432
101598
|
const s = params.toString();
|
|
101433
101599
|
return s ? `?${s}` : "";
|
|
101434
101600
|
}
|
|
101435
|
-
async function
|
|
101601
|
+
async function fetchGoogleAnalysisHttp(label, factory, http2) {
|
|
101602
|
+
return fetchWithRetry(factory, {
|
|
101603
|
+
label,
|
|
101604
|
+
policy: { ...DEFAULT_RETRY_POLICY, ...http2?.retry },
|
|
101605
|
+
signal: http2?.signal
|
|
101606
|
+
});
|
|
101607
|
+
}
|
|
101608
|
+
async function fetchJson(config, pathWithQuery, verbose, label = pathWithQuery, http2) {
|
|
101436
101609
|
const url = `${config.googleApiUrl}${pathWithQuery}`;
|
|
101437
|
-
return apiFetch2(url, config, {}, verbose);
|
|
101610
|
+
return fetchGoogleAnalysisHttp(label, () => apiFetch2(url, config, {}, verbose), http2);
|
|
101611
|
+
}
|
|
101612
|
+
async function fetchMainPlatformJson(url, config, verbose, label, http2) {
|
|
101613
|
+
return fetchGoogleAnalysisHttp(label, () => apiFetch2(url, config, {}, verbose), http2);
|
|
101438
101614
|
}
|
|
101439
101615
|
function deviceBidModifierLookupKey(campaignId, adGroupId, deviceType) {
|
|
101440
101616
|
return `${campaignId ?? ""}\0${adGroupId ?? ""}\0${deviceType ?? ""}`;
|
|
@@ -101482,54 +101658,55 @@ function rowsFromAccountDailyReportsEnvelope(raw, mediaCustomerId) {
|
|
|
101482
101658
|
const list = block?.data;
|
|
101483
101659
|
return Array.isArray(list) ? list : [];
|
|
101484
101660
|
}
|
|
101485
|
-
async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2) {
|
|
101661
|
+
async function fetchGoogleAnalysisSectionJson(config, fullPath, verbose, name2, label, http2) {
|
|
101662
|
+
const fetchSection = () => fetchJson(config, fullPath, verbose, label, http2);
|
|
101486
101663
|
switch (name2) {
|
|
101487
101664
|
case "overview":
|
|
101488
|
-
return
|
|
101665
|
+
return fetchSection();
|
|
101489
101666
|
case "keywords":
|
|
101490
|
-
return
|
|
101667
|
+
return fetchSection();
|
|
101491
101668
|
case "search-terms":
|
|
101492
|
-
return
|
|
101669
|
+
return fetchSection();
|
|
101493
101670
|
case "campaigns":
|
|
101494
|
-
return
|
|
101671
|
+
return fetchSection();
|
|
101495
101672
|
case "campaign-hour":
|
|
101496
|
-
return
|
|
101673
|
+
return fetchSection();
|
|
101497
101674
|
case "ads":
|
|
101498
|
-
return
|
|
101675
|
+
return fetchSection();
|
|
101499
101676
|
case "extensions":
|
|
101500
|
-
return
|
|
101677
|
+
return fetchSection();
|
|
101501
101678
|
case "devices":
|
|
101502
|
-
return
|
|
101679
|
+
return fetchSection();
|
|
101503
101680
|
case "geographic":
|
|
101504
|
-
return
|
|
101681
|
+
return fetchSection();
|
|
101505
101682
|
case "geo-matched":
|
|
101506
|
-
return
|
|
101683
|
+
return fetchSection();
|
|
101507
101684
|
case "campaign-geo":
|
|
101508
|
-
return
|
|
101685
|
+
return fetchSection();
|
|
101509
101686
|
case "campaign-geo-matched":
|
|
101510
|
-
return
|
|
101687
|
+
return fetchSection();
|
|
101511
101688
|
case "campaign-device":
|
|
101512
|
-
return
|
|
101689
|
+
return fetchSection();
|
|
101513
101690
|
case "audience":
|
|
101514
|
-
return
|
|
101691
|
+
return fetchSection();
|
|
101515
101692
|
case "asset-images":
|
|
101516
|
-
return
|
|
101693
|
+
return fetchSection();
|
|
101517
101694
|
case "videos":
|
|
101518
|
-
return
|
|
101695
|
+
return fetchSection();
|
|
101519
101696
|
case "resource-counts":
|
|
101520
|
-
return
|
|
101697
|
+
return fetchSection();
|
|
101521
101698
|
case "conversion-actions":
|
|
101522
|
-
return
|
|
101699
|
+
return fetchSection();
|
|
101523
101700
|
case "gold-account":
|
|
101524
|
-
return
|
|
101701
|
+
return fetchSection();
|
|
101525
101702
|
case "ads-index":
|
|
101526
|
-
return
|
|
101703
|
+
return fetchSection();
|
|
101527
101704
|
case "final-urls":
|
|
101528
|
-
return
|
|
101705
|
+
return fetchSection();
|
|
101529
101706
|
case "dimension-summary":
|
|
101530
|
-
return
|
|
101707
|
+
return fetchSection();
|
|
101531
101708
|
case "campaign-types":
|
|
101532
|
-
return
|
|
101709
|
+
return fetchSection();
|
|
101533
101710
|
default:
|
|
101534
101711
|
return assertNever(name2, "google-analysis");
|
|
101535
101712
|
}
|
|
@@ -101544,7 +101721,7 @@ function manifestDateRange(def, start, end) {
|
|
|
101544
101721
|
const { startDate, endDate } = resolveDateRange2(start, end);
|
|
101545
101722
|
return { mode: "range", start: startDate, end: endDate };
|
|
101546
101723
|
}
|
|
101547
|
-
async function fetchSectionPayload(def, opts, config, id) {
|
|
101724
|
+
async function fetchSectionPayload(def, opts, config, id, http2) {
|
|
101548
101725
|
const extras = {};
|
|
101549
101726
|
if (def.keywordOptions) {
|
|
101550
101727
|
if (typeof opts.limit === "number" && opts.limit > 0) {
|
|
@@ -101571,9 +101748,10 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101571
101748
|
const q = `?${new URLSearchParams({ startDate, endDate }).toString()}`;
|
|
101572
101749
|
const videoPath = `/reporting/media-account/${id}/Videos${q}`;
|
|
101573
101750
|
const imagePath = `/reporting/media-account/${id}/CampaignAssetView${q}`;
|
|
101751
|
+
const verbose = !!opts.verbose;
|
|
101574
101752
|
const [images, videos] = await Promise.all([
|
|
101575
|
-
fetchJson(config, imagePath,
|
|
101576
|
-
fetchJson(config, videoPath,
|
|
101753
|
+
fetchJson(config, imagePath, verbose, `materials/images/${id}`, http2),
|
|
101754
|
+
fetchJson(config, videoPath, verbose, `materials/videos/${id}`, http2)
|
|
101577
101755
|
]);
|
|
101578
101756
|
const merged = { images, videos };
|
|
101579
101757
|
return annotateZhFields(
|
|
@@ -101587,12 +101765,21 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101587
101765
|
if (def.name === "campaign-device") {
|
|
101588
101766
|
const sectionPath2 = def.path(id);
|
|
101589
101767
|
const query2 = buildSearchParams(def, opts.start, opts.end, extras);
|
|
101768
|
+
const verbose = !!opts.verbose;
|
|
101590
101769
|
const [report, modifiers] = await Promise.all([
|
|
101591
|
-
fetchJson(
|
|
101770
|
+
fetchJson(
|
|
101771
|
+
config,
|
|
101772
|
+
`${sectionPath2}${query2}`,
|
|
101773
|
+
verbose,
|
|
101774
|
+
`campaign-device/${id}`,
|
|
101775
|
+
http2
|
|
101776
|
+
),
|
|
101592
101777
|
fetchJson(
|
|
101593
101778
|
config,
|
|
101594
101779
|
`/campaignmanagement/${id}/BidModifiers/Devices`,
|
|
101595
|
-
|
|
101780
|
+
verbose,
|
|
101781
|
+
`campaign-device/bid-modifiers/${id}`,
|
|
101782
|
+
http2
|
|
101596
101783
|
)
|
|
101597
101784
|
]);
|
|
101598
101785
|
const data2 = mergeDeviceBidModifiersIntoReport(report, modifiers);
|
|
@@ -101606,13 +101793,20 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101606
101793
|
}
|
|
101607
101794
|
if (def.name === "daily-metrics") {
|
|
101608
101795
|
const { startDate, endDate } = resolveDateRange2(opts.start, opts.end);
|
|
101796
|
+
const { startInstant, endInstant } = resolveDailyMetricsQueryTimes(startDate, endDate);
|
|
101609
101797
|
const params = new URLSearchParams({
|
|
101610
101798
|
mediaCustomerIds: id,
|
|
101611
|
-
startDate:
|
|
101612
|
-
endDate:
|
|
101799
|
+
startDate: startInstant,
|
|
101800
|
+
endDate: endInstant
|
|
101613
101801
|
});
|
|
101614
101802
|
const url = `${config.apiBaseUrl}/report/media-account/google/account-daily-reports?${params.toString()}`;
|
|
101615
|
-
const raw = await
|
|
101803
|
+
const raw = await fetchMainPlatformJson(
|
|
101804
|
+
url,
|
|
101805
|
+
config,
|
|
101806
|
+
!!opts.verbose,
|
|
101807
|
+
`daily-metrics/${id}`,
|
|
101808
|
+
http2
|
|
101809
|
+
);
|
|
101616
101810
|
const rows = rowsFromAccountDailyReportsEnvelope(raw, id);
|
|
101617
101811
|
return annotateZhFields(
|
|
101618
101812
|
normalizeMoneyScales(
|
|
@@ -101628,7 +101822,9 @@ async function fetchSectionPayload(def, opts, config, id) {
|
|
|
101628
101822
|
config,
|
|
101629
101823
|
`${sectionPath}${query}`,
|
|
101630
101824
|
!!opts.verbose,
|
|
101631
|
-
def.name
|
|
101825
|
+
def.name,
|
|
101826
|
+
`${def.name}/${id}`,
|
|
101827
|
+
http2
|
|
101632
101828
|
);
|
|
101633
101829
|
let payload = stripLegacyGoogleFieldsIfV2Present(data);
|
|
101634
101830
|
payload = normalizeRateScales(payload, def.name);
|
|
@@ -101665,7 +101861,8 @@ async function fetchOneGoogleSection(opts) {
|
|
|
101665
101861
|
conversionsGreater: opts.conversionsGreater
|
|
101666
101862
|
},
|
|
101667
101863
|
opts.config,
|
|
101668
|
-
opts.accountId
|
|
101864
|
+
opts.accountId,
|
|
101865
|
+
{ retry: opts.retry, signal: opts.signal }
|
|
101669
101866
|
);
|
|
101670
101867
|
return {
|
|
101671
101868
|
payload,
|
|
@@ -101679,156 +101876,14 @@ var init_fetch = __esm({
|
|
|
101679
101876
|
"use strict";
|
|
101680
101877
|
init_auth();
|
|
101681
101878
|
init_strip_legacy_google_fields();
|
|
101879
|
+
init_http_retry();
|
|
101682
101880
|
init_sections();
|
|
101683
101881
|
init_normalize_impression_shares();
|
|
101684
101882
|
init_normalize_rates();
|
|
101685
101883
|
init_normalize_money();
|
|
101686
101884
|
init_normalize_overview();
|
|
101687
101885
|
init_translate_fields();
|
|
101688
|
-
|
|
101689
|
-
});
|
|
101690
|
-
|
|
101691
|
-
// src/utils/http-retry.ts
|
|
101692
|
-
function classifyError(err) {
|
|
101693
|
-
const status = parseHttpStatusFromError(err);
|
|
101694
|
-
if (status === 401) return { class: "abort", status };
|
|
101695
|
-
if (status === void 0) {
|
|
101696
|
-
return { class: "retryable" };
|
|
101697
|
-
}
|
|
101698
|
-
if (status === 408 || status === 429) return { class: "retryable", status };
|
|
101699
|
-
if (status >= 500 && status <= 599) return { class: "retryable", status };
|
|
101700
|
-
if (status >= 400 && status <= 499) return { class: "permanent", status };
|
|
101701
|
-
return { class: "permanent", status };
|
|
101702
|
-
}
|
|
101703
|
-
function computeBackoffMs(attempt, policy) {
|
|
101704
|
-
const exp = policy.baseMs * 2 ** Math.max(0, attempt - 1);
|
|
101705
|
-
const capped = Math.min(exp, policy.maxBackoffMs);
|
|
101706
|
-
const jitter = Math.floor(Math.random() * policy.baseMs);
|
|
101707
|
-
return capped + jitter;
|
|
101708
|
-
}
|
|
101709
|
-
function sleep3(ms, signal) {
|
|
101710
|
-
return new Promise((resolve15, reject) => {
|
|
101711
|
-
if (signal?.aborted) {
|
|
101712
|
-
reject(new Error("retry sleep aborted"));
|
|
101713
|
-
return;
|
|
101714
|
-
}
|
|
101715
|
-
const timer = setTimeout(() => {
|
|
101716
|
-
signal?.removeEventListener("abort", onAbort);
|
|
101717
|
-
resolve15();
|
|
101718
|
-
}, ms);
|
|
101719
|
-
const onAbort = () => {
|
|
101720
|
-
clearTimeout(timer);
|
|
101721
|
-
reject(new Error("retry sleep aborted"));
|
|
101722
|
-
};
|
|
101723
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
101724
|
-
});
|
|
101725
|
-
}
|
|
101726
|
-
async function withTaskTimeout(factory, timeoutMs, label) {
|
|
101727
|
-
if (!timeoutMs || timeoutMs <= 0) return factory();
|
|
101728
|
-
let timer;
|
|
101729
|
-
try {
|
|
101730
|
-
return await Promise.race([
|
|
101731
|
-
factory(),
|
|
101732
|
-
new Promise((_, reject) => {
|
|
101733
|
-
timer = setTimeout(() => {
|
|
101734
|
-
reject(new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${(timeoutMs / 1e3).toFixed(0)} \u79D2\uFF09\uFF1A${label}`));
|
|
101735
|
-
}, timeoutMs);
|
|
101736
|
-
})
|
|
101737
|
-
]);
|
|
101738
|
-
} finally {
|
|
101739
|
-
if (timer) clearTimeout(timer);
|
|
101740
|
-
}
|
|
101741
|
-
}
|
|
101742
|
-
async function fetchWithRetry(factory, options) {
|
|
101743
|
-
const policy = { ...DEFAULT_RETRY_POLICY, ...options.policy ?? {} };
|
|
101744
|
-
let lastErr;
|
|
101745
|
-
let lastStatus;
|
|
101746
|
-
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
101747
|
-
if (options.signal?.aborted) {
|
|
101748
|
-
throw new AbortRunError({
|
|
101749
|
-
message: `\u4EFB\u52A1\u88AB\u5916\u90E8\u4FE1\u53F7\u4E2D\u6B62\uFF1A${options.label}`
|
|
101750
|
-
});
|
|
101751
|
-
}
|
|
101752
|
-
try {
|
|
101753
|
-
return await withTaskTimeout(factory, policy.taskTimeoutMs, options.label);
|
|
101754
|
-
} catch (raw) {
|
|
101755
|
-
const err = raw instanceof Error ? raw : new Error(String(raw));
|
|
101756
|
-
const cls = classifyError(err);
|
|
101757
|
-
lastErr = err;
|
|
101758
|
-
lastStatus = cls.status;
|
|
101759
|
-
if (cls.class === "abort") {
|
|
101760
|
-
throw new AbortRunError({
|
|
101761
|
-
message: `Token \u5931\u6548\u6216\u81F4\u547D\u9519\u8BEF\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
101762
|
-
status: cls.status,
|
|
101763
|
-
cause: err
|
|
101764
|
-
});
|
|
101765
|
-
}
|
|
101766
|
-
if (cls.class === "permanent" || attempt >= policy.maxAttempts) {
|
|
101767
|
-
throw new HttpRetryError({
|
|
101768
|
-
message: cls.class === "permanent" ? `\u6C38\u4E45\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}` : `\u91CD\u8BD5 ${attempt} \u6B21\u4ECD\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${err.message}`,
|
|
101769
|
-
class: cls.class === "permanent" ? "permanent" : "retryable",
|
|
101770
|
-
status: cls.status,
|
|
101771
|
-
attempts: attempt,
|
|
101772
|
-
cause: err
|
|
101773
|
-
});
|
|
101774
|
-
}
|
|
101775
|
-
const backoffMs = computeBackoffMs(attempt, policy);
|
|
101776
|
-
options.onRetry?.({ attempt, backoffMs, status: cls.status, error: err });
|
|
101777
|
-
try {
|
|
101778
|
-
await sleep3(backoffMs, options.signal);
|
|
101779
|
-
} catch {
|
|
101780
|
-
throw new AbortRunError({
|
|
101781
|
-
message: `\u91CD\u8BD5\u7B49\u5F85\u88AB\u4E2D\u6B62\uFF1A${options.label}`,
|
|
101782
|
-
cause: err
|
|
101783
|
-
});
|
|
101784
|
-
}
|
|
101785
|
-
}
|
|
101786
|
-
}
|
|
101787
|
-
throw new HttpRetryError({
|
|
101788
|
-
message: `\u672A\u77E5\u91CD\u8BD5\u5931\u8D25\uFF08${options.label}\uFF09\uFF1A${lastErr?.message ?? "no error"}`,
|
|
101789
|
-
class: "retryable",
|
|
101790
|
-
status: lastStatus,
|
|
101791
|
-
attempts: policy.maxAttempts,
|
|
101792
|
-
cause: lastErr
|
|
101793
|
-
});
|
|
101794
|
-
}
|
|
101795
|
-
var HttpRetryError, AbortRunError, DEFAULT_RETRY_POLICY;
|
|
101796
|
-
var init_http_retry = __esm({
|
|
101797
|
-
"src/utils/http-retry.ts"() {
|
|
101798
|
-
"use strict";
|
|
101799
|
-
init_dist();
|
|
101800
|
-
HttpRetryError = class extends Error {
|
|
101801
|
-
class;
|
|
101802
|
-
status;
|
|
101803
|
-
attempts;
|
|
101804
|
-
cause;
|
|
101805
|
-
constructor(opts) {
|
|
101806
|
-
super(opts.message);
|
|
101807
|
-
this.name = "HttpRetryError";
|
|
101808
|
-
this.class = opts.class;
|
|
101809
|
-
this.status = opts.status;
|
|
101810
|
-
this.attempts = opts.attempts;
|
|
101811
|
-
this.cause = opts.cause;
|
|
101812
|
-
}
|
|
101813
|
-
};
|
|
101814
|
-
AbortRunError = class extends HttpRetryError {
|
|
101815
|
-
constructor(opts) {
|
|
101816
|
-
super({
|
|
101817
|
-
message: opts.message,
|
|
101818
|
-
class: "abort",
|
|
101819
|
-
status: opts.status,
|
|
101820
|
-
attempts: 1,
|
|
101821
|
-
cause: opts.cause
|
|
101822
|
-
});
|
|
101823
|
-
this.name = "AbortRunError";
|
|
101824
|
-
}
|
|
101825
|
-
};
|
|
101826
|
-
DEFAULT_RETRY_POLICY = {
|
|
101827
|
-
maxAttempts: 3,
|
|
101828
|
-
baseMs: 1e3,
|
|
101829
|
-
maxBackoffMs: 3e4,
|
|
101830
|
-
taskTimeoutMs: 6e4
|
|
101831
|
-
};
|
|
101886
|
+
init_daily_metrics_range();
|
|
101832
101887
|
}
|
|
101833
101888
|
});
|
|
101834
101889
|
|
|
@@ -102260,24 +102315,19 @@ async function runBatch(opts) {
|
|
|
102260
102315
|
await recordTaskTransition(opts.paths, inner, task, "running", { attempts: 0 });
|
|
102261
102316
|
const t0 = performance2.now();
|
|
102262
102317
|
try {
|
|
102263
|
-
const result = await
|
|
102264
|
-
|
|
102265
|
-
|
|
102266
|
-
|
|
102267
|
-
|
|
102268
|
-
|
|
102269
|
-
|
|
102270
|
-
|
|
102271
|
-
|
|
102272
|
-
|
|
102273
|
-
|
|
102274
|
-
|
|
102275
|
-
|
|
102276
|
-
label: `${task.section}/${task.accountId}`,
|
|
102277
|
-
policy: opts.retry,
|
|
102278
|
-
signal: aborterToken.signal
|
|
102279
|
-
}
|
|
102280
|
-
);
|
|
102318
|
+
const result = await fetcher({
|
|
102319
|
+
config: opts.config,
|
|
102320
|
+
accountId: task.accountId,
|
|
102321
|
+
section: task.section,
|
|
102322
|
+
start: opts.start,
|
|
102323
|
+
end: opts.end,
|
|
102324
|
+
limit: opts.keywordLimit,
|
|
102325
|
+
costGreater: opts.campaignGeoCostGreater,
|
|
102326
|
+
clickGreater: opts.campaignGeoClickGreater,
|
|
102327
|
+
conversionsGreater: opts.campaignGeoConversionsGreater,
|
|
102328
|
+
retry: opts.retry,
|
|
102329
|
+
signal: aborterToken.signal
|
|
102330
|
+
});
|
|
102281
102331
|
await writer({
|
|
102282
102332
|
snapshotDir: path25.join(opts.paths.resultsDir, sanitizeAccountSegment(task.accountId)),
|
|
102283
102333
|
section: task.section,
|
|
@@ -103046,6 +103096,51 @@ async function runWithConcurrency(tasks, concurrency) {
|
|
|
103046
103096
|
await Promise.all(workers);
|
|
103047
103097
|
return results;
|
|
103048
103098
|
}
|
|
103099
|
+
function sectionFetchErrorMessage(err) {
|
|
103100
|
+
return err instanceof Error ? err.message : String(err);
|
|
103101
|
+
}
|
|
103102
|
+
async function fetchAndWriteSection(def, opts, config, id, cliVersion) {
|
|
103103
|
+
const t0 = performance4.now();
|
|
103104
|
+
try {
|
|
103105
|
+
const { payload } = await fetchOneGoogleSection({
|
|
103106
|
+
config,
|
|
103107
|
+
accountId: id,
|
|
103108
|
+
section: def.name,
|
|
103109
|
+
start: opts.start,
|
|
103110
|
+
end: opts.end,
|
|
103111
|
+
verbose: opts.verbose,
|
|
103112
|
+
limit: opts.limit,
|
|
103113
|
+
noOrderByCost: opts.noOrderByCost,
|
|
103114
|
+
level: opts.level,
|
|
103115
|
+
audienceType: opts.audienceType,
|
|
103116
|
+
costGreater: opts.costGreater,
|
|
103117
|
+
clickGreater: opts.clickGreater,
|
|
103118
|
+
conversionsGreater: opts.conversionsGreater
|
|
103119
|
+
});
|
|
103120
|
+
const summary = await writeGoogleAnalysisSnapshot({
|
|
103121
|
+
snapshotDir: opts.jsonOut,
|
|
103122
|
+
section: def.name,
|
|
103123
|
+
accountId: id,
|
|
103124
|
+
dateRange: manifestDateRange(def, opts.start, opts.end),
|
|
103125
|
+
payload,
|
|
103126
|
+
cliVersion,
|
|
103127
|
+
endpointHint: endpointHintForSection(def)
|
|
103128
|
+
});
|
|
103129
|
+
return {
|
|
103130
|
+
section: def.name,
|
|
103131
|
+
ok: true,
|
|
103132
|
+
elapsedMs: performance4.now() - t0,
|
|
103133
|
+
file: summary.writtenFiles[0]
|
|
103134
|
+
};
|
|
103135
|
+
} catch (err) {
|
|
103136
|
+
return {
|
|
103137
|
+
section: def.name,
|
|
103138
|
+
ok: false,
|
|
103139
|
+
elapsedMs: performance4.now() - t0,
|
|
103140
|
+
error: sectionFetchErrorMessage(err)
|
|
103141
|
+
};
|
|
103142
|
+
}
|
|
103143
|
+
}
|
|
103049
103144
|
async function runAllSections(opts) {
|
|
103050
103145
|
const config = loadConfig(opts.token);
|
|
103051
103146
|
const id = opts.account.split(",")[0].trim();
|
|
@@ -103070,35 +103165,20 @@ async function runAllSections(opts) {
|
|
|
103070
103165
|
);
|
|
103071
103166
|
const cliVersion = getCurrentVersion2();
|
|
103072
103167
|
const overallT0 = performance4.now();
|
|
103073
|
-
const tasks = targets.map(
|
|
103074
|
-
|
|
103075
|
-
|
|
103076
|
-
|
|
103077
|
-
|
|
103078
|
-
|
|
103079
|
-
|
|
103080
|
-
|
|
103081
|
-
|
|
103082
|
-
|
|
103083
|
-
|
|
103084
|
-
|
|
103085
|
-
});
|
|
103086
|
-
return {
|
|
103087
|
-
section: def.name,
|
|
103088
|
-
ok: true,
|
|
103089
|
-
elapsedMs: performance4.now() - t0,
|
|
103090
|
-
file: summary2.writtenFiles[0]
|
|
103091
|
-
};
|
|
103092
|
-
} catch (err) {
|
|
103093
|
-
return {
|
|
103094
|
-
section: def.name,
|
|
103095
|
-
ok: false,
|
|
103096
|
-
elapsedMs: performance4.now() - t0,
|
|
103097
|
-
error: err instanceof Error ? err.message : String(err)
|
|
103098
|
-
};
|
|
103168
|
+
const tasks = targets.map(
|
|
103169
|
+
(def) => () => fetchAndWriteSection(def, opts, config, id, cliVersion)
|
|
103170
|
+
);
|
|
103171
|
+
let results = await runWithConcurrency(tasks, concurrency);
|
|
103172
|
+
const failedIndices = results.map((r, i) => r.ok ? -1 : i).filter((i) => i >= 0);
|
|
103173
|
+
if (failedIndices.length > 0) {
|
|
103174
|
+
console.error(`
|
|
103175
|
+
\u26A0\uFE0F ${failedIndices.length} \u4E2A\u7EF4\u5EA6\u9996\u8F6E\u5931\u8D25\uFF0C\u4E32\u884C\u4E8C\u8F6E\u91CD\u8BD5\u2026
|
|
103176
|
+
`);
|
|
103177
|
+
for (const i of failedIndices) {
|
|
103178
|
+
const retry = await fetchAndWriteSection(targets[i], opts, config, id, cliVersion);
|
|
103179
|
+
if (retry.ok) results = [...results.slice(0, i), retry, ...results.slice(i + 1)];
|
|
103099
103180
|
}
|
|
103100
|
-
}
|
|
103101
|
-
const results = await runWithConcurrency(tasks, concurrency);
|
|
103181
|
+
}
|
|
103102
103182
|
const succeeded = results.filter((r) => r.ok).length;
|
|
103103
103183
|
const failed = results.length - succeeded;
|
|
103104
103184
|
const absoluteSnapshotDir = path27.resolve(opts.jsonOut);
|
|
@@ -124804,12 +124884,11 @@ import path18 from "path";
|
|
|
124804
124884
|
// src/commands/market-analysis/report-content.ts
|
|
124805
124885
|
var SILUZAN_ECHARTS_CDN_URL = "https://staticpn.siluzan.com/assets/slz/homeCDN/echarts.js";
|
|
124806
124886
|
var MARKET_REPORT_DIMENSIONS = [
|
|
124807
|
-
// ── 封面与元信息 ──
|
|
124808
124887
|
{
|
|
124809
124888
|
id: "cover",
|
|
124810
124889
|
chapter: "\u5C01\u9762",
|
|
124811
|
-
dimension: "\u62A5\u544A\u6807\u9898\u542B\
|
|
124812
|
-
hint: "\u987B\u542B\u300C\u6218\u7565\u5E02\u573A\u5206\u6790\u300D\
|
|
124890
|
+
dimension: "\u62A5\u544A\u6807\u9898\u542B\u6218\u7565/\u884C\u4E1A/\u5E02\u573A\u5206\u6790\u6807\u8BC6",
|
|
124891
|
+
hint: "\u987B\u542B\u300C\u6218\u7565\u5E02\u573A\u5206\u6790\u300D\u300C\u884C\u4E1A\u5206\u6790\u62A5\u544A\u300D\u300C\u5E02\u573A\u5206\u6790\u62A5\u544A\u300D\u6216\u300C\u884C\u4E1A\u8C03\u67E5\u300D\u7B49"
|
|
124813
124892
|
},
|
|
124814
124893
|
{
|
|
124815
124894
|
id: "cover-kpi",
|
|
@@ -124817,7 +124896,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124817
124896
|
dimension: "\u6838\u5FC3\u7ED3\u8BBA\u6216 KPI \u6458\u8981",
|
|
124818
124897
|
hint: "\u5C01\u9762\u987B\u6709\u53EF\u626B\u8BFB\u7684\u7ED3\u8BBA\u6BB5\u6216 metric \u5361\u7247\uFF0C\u975E\u4EC5\u6807\u9898"
|
|
124819
124898
|
},
|
|
124820
|
-
// ── 一、市场与趋势诊断 ──
|
|
124821
124899
|
{
|
|
124822
124900
|
id: "ch1",
|
|
124823
124901
|
chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
|
|
@@ -124840,7 +124918,7 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124840
124918
|
id: "ch1-target-geo",
|
|
124841
124919
|
chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
|
|
124842
124920
|
dimension: "\u76EE\u6807\u5E02\u573A\u56FD\u5BB6/\u533A\u57DF\u62C6\u89E3",
|
|
124843
|
-
hint: "\u987B\u6309\u56FD\u5BB6\u6216\u533A\u57DF\u5BF9\u6BD4\u89C4\u6A21\u3001\u589E\u957F\u3001\u4F18\u5148\u7EA7\uFF08\u8868\u683C\u6216\u5206\u6BB5\uFF09"
|
|
124921
|
+
hint: "\u987B\u6309\u56FD\u5BB6\u6216\u533A\u57DF\u5BF9\u6BD4\u89C4\u6A21\u3001\u589E\u957F\u3001\u4F18\u5148\u7EA7\uFF08\u8868\u683C\u6216\u5206\u6BB5\uFF09\uFF1B\u4E0E collect \u76EE\u6807\u5E02\u573A\u4E00\u81F4\u66F4\u4F73"
|
|
124844
124922
|
},
|
|
124845
124923
|
{
|
|
124846
124924
|
id: "ch1-pestel",
|
|
@@ -124854,7 +124932,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124854
124932
|
dimension: "3\u20135 \u5E74\u8D8B\u52BF\u9884\u6D4B\u4E0E\u5047\u8BBE",
|
|
124855
124933
|
hint: "\u987B\u542B\u300C\u9884\u6D4B\u300D\u6216\u300C3\u300D\u5E74/\u300C5\u300D\u5E74\u8D8B\u52BF\u8868\u8FF0"
|
|
124856
124934
|
},
|
|
124857
|
-
// ── 二、目标行业深度洞察 ──
|
|
124858
124935
|
{
|
|
124859
124936
|
id: "ch2",
|
|
124860
124937
|
chapter: "\u4E8C\u3001\u76EE\u6807\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF",
|
|
@@ -124871,7 +124948,7 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124871
124948
|
id: "ch2-compliance",
|
|
124872
124949
|
chapter: "\u4E8C\u3001\u76EE\u6807\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF",
|
|
124873
124950
|
dimension: "\u6CD5\u89C4\u73AF\u5883\u4E0E\u5408\u89C4\u8981\u6C42",
|
|
124874
|
-
hint: "\u987B\u542B\u6CD5\u89C4/\u5408\u89C4\u8868\uFF0C\u542B\u5E94\u5BF9\u7B56\u7565\uFF1B\u5B9C\u542B\u5408\u89C4\
|
|
124951
|
+
hint: "\u987B\u542B\u6CD5\u89C4/\u5408\u89C4\u8868\uFF0C\u542B\u5E94\u5BF9\u7B56\u7565\uFF1B\u5B9C\u542B\u5408\u89C4\u6210\u672C\u6216\u6EA2\u4EF7\u4F30\u7B97"
|
|
124875
124952
|
},
|
|
124876
124953
|
{
|
|
124877
124954
|
id: "ch2-bpmn",
|
|
@@ -124879,7 +124956,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124879
124956
|
dimension: "\u5178\u578B\u573A\u666F\u4E0E BPMN \u6D41\u7A0B\u5BF9\u6BD4",
|
|
124880
124957
|
hint: "\u987B\u542B\u300C\u539F\u6D41\u7A0B\u300D\u4E0E\u300C\u65B0\u6D41\u7A0B\u300D\u6216 BPMN \u6587\u672C + \u6548\u7387/\u6210\u672C\u91CF\u5316"
|
|
124881
124958
|
},
|
|
124882
|
-
// ── 三、目标受众与场景 ──
|
|
124883
124959
|
{
|
|
124884
124960
|
id: "ch3",
|
|
124885
124961
|
chapter: "\u4E09\u3001\u76EE\u6807\u53D7\u4F17\u4E0E\u573A\u666F",
|
|
@@ -124898,7 +124974,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124898
124974
|
dimension: "\u4EA7\u54C1\u5E94\u7528\u573A\u666F\u4E0E\u4EF7\u503C\u5206\u6790",
|
|
124899
124975
|
hint: "\u987B\u542B\u5E94\u7528\u573A\u666F\u3001\u5DEE\u5F02\u5316\u3001\u7528\u6237\u4EF7\u503C\u4E0E\u4F01\u4E1A\u4EF7\u503C"
|
|
124900
124976
|
},
|
|
124901
|
-
// ── 四、竞争策略与定位 ──
|
|
124902
124977
|
{
|
|
124903
124978
|
id: "ch4",
|
|
124904
124979
|
chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
|
|
@@ -124909,13 +124984,13 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124909
124984
|
id: "ch4-competitors",
|
|
124910
124985
|
chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
|
|
124911
124986
|
dimension: "\u4E3B\u8981\u7ADE\u54C1\u8BC6\u522B",
|
|
124912
|
-
hint: "\u987B\u70B9\u540D \u22653 \u4E2A\u7ADE\u54C1\u6216\u5BF9\u6807\u54C1\u724C"
|
|
124987
|
+
hint: "\u987B\u70B9\u540D \u22653 \u4E2A\u7ADE\u54C1\u6216\u5BF9\u6807\u4F01\u4E1A\uFF08\u4E0D\u9650\u5B9A\u884C\u4E1A\u54C1\u724C\uFF09"
|
|
124913
124988
|
},
|
|
124914
124989
|
{
|
|
124915
124990
|
id: "ch4-compare-table",
|
|
124916
124991
|
chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
|
|
124917
|
-
dimension: "\u529F\u80FD\u4E0E\u6027\u80FD\u5BF9\u6BD4\u8868\uFF08\u226510 \u9879 \xD7 \u22653 \
|
|
124918
|
-
hint: "\u987B\u6709\u7ADE\u54C1\u5BF9\u6BD4\u8868\
|
|
124992
|
+
dimension: "\u529F\u80FD\u4E0E\u6027\u80FD\u5BF9\u6BD4\u8868\uFF08\u226510 \u9879 \xD7 \u22653 \u4E3B\u4F53\uFF09",
|
|
124993
|
+
hint: "\u987B\u6709\u7ADE\u54C1\u5BF9\u6BD4\u8868\uFF1A\u22654 \u5217\uFF08\u7EF4\u5EA6+\u591A\u65B9\uFF09\u4E14 \u226511 \u884C"
|
|
124919
124994
|
},
|
|
124920
124995
|
{
|
|
124921
124996
|
id: "ch4-gaps",
|
|
@@ -124929,7 +125004,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124929
125004
|
dimension: "\u6838\u5FC3\u5B9A\u4F4D\u9648\u8FF0\u4E0E\u5DEE\u5F02\u5316\u7EF4\u5EA6",
|
|
124930
125005
|
hint: "\u987B\u6709\u4E00\u53E5\u5B9A\u4F4D\u9648\u8FF0 + \u591A\u4E2A\u5DEE\u5F02\u5316\u7EF4\u5EA6"
|
|
124931
125006
|
},
|
|
124932
|
-
// ── 五、增长与品牌策略 ──
|
|
124933
125007
|
{
|
|
124934
125008
|
id: "ch5",
|
|
124935
125009
|
chapter: "\u4E94\u3001\u589E\u957F\u4E0E\u54C1\u724C\u7B56\u7565",
|
|
@@ -124954,7 +125028,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124954
125028
|
dimension: "\u8425\u9500\u4E0E\u9500\u552E\u6218\u672F",
|
|
124955
125029
|
hint: "\u987B\u542B\u5185\u5BB9\u8425\u9500/\u6E20\u9053/KOL/\u9500\u552E\u6A21\u5F0F\u7B49\u6218\u672F\u63CF\u8FF0"
|
|
124956
125030
|
},
|
|
124957
|
-
// ── 六、实施、资源与风险 ──
|
|
124958
125031
|
{
|
|
124959
125032
|
id: "ch6",
|
|
124960
125033
|
chapter: "\u516D\u3001\u5B9E\u65BD\u3001\u8D44\u6E90\u4E0E\u98CE\u9669",
|
|
@@ -124979,7 +125052,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
124979
125052
|
dimension: "\u98CE\u9669\u77E9\u9635\u4E0E\u5E94\u5BF9\u7B56\u7565",
|
|
124980
125053
|
hint: "\u987B\u542B\u5E02\u573A/\u7ADE\u4E89/\u6280\u672F/\u8FD0\u8425\u7B49\u98CE\u9669 + \u6BCF\u7C7B\u5E94\u5BF9\u63AA\u65BD"
|
|
124981
125054
|
},
|
|
124982
|
-
// ── 附录 ──
|
|
124983
125055
|
{
|
|
124984
125056
|
id: "appendix",
|
|
124985
125057
|
chapter: "\u9644\u5F55",
|
|
@@ -125004,7 +125076,6 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
125004
125076
|
dimension: "\u672F\u8BED\u8868\uFF08ROI/CAGR/GTM/BPMN \u7B49\uFF09",
|
|
125005
125077
|
hint: "\u987B\u89E3\u91CA\u81F3\u5C11 3 \u4E2A\u7F29\u5199"
|
|
125006
125078
|
},
|
|
125007
|
-
// ── 呈现规范(原始 prompt HTML 要求)──
|
|
125008
125079
|
{
|
|
125009
125080
|
id: "viz-echarts",
|
|
125010
125081
|
chapter: "\u5448\u73B0",
|
|
@@ -125018,6 +125089,44 @@ var MARKET_REPORT_DIMENSIONS = [
|
|
|
125018
125089
|
hint: "\u987B\u542B Source: \u6216\u300C\u6765\u6E90\u300D\u811A\u6CE8"
|
|
125019
125090
|
}
|
|
125020
125091
|
];
|
|
125092
|
+
function stripHtmlTags(html) {
|
|
125093
|
+
return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ");
|
|
125094
|
+
}
|
|
125095
|
+
function countTableColumns(tableHtml) {
|
|
125096
|
+
const firstRow = tableHtml.match(/<tr[\s\S]*?<\/tr>/i)?.[0] ?? "";
|
|
125097
|
+
const th = (firstRow.match(/<th/gi) ?? []).length;
|
|
125098
|
+
const td = (firstRow.match(/<td/gi) ?? []).length;
|
|
125099
|
+
return Math.max(th, td);
|
|
125100
|
+
}
|
|
125101
|
+
function isCompareTable(tableHtml, html, tableIndex) {
|
|
125102
|
+
const inTable = /对比|竞品|功能|性能|维度|指标|参数/.test(tableHtml);
|
|
125103
|
+
if (inTable) return true;
|
|
125104
|
+
const windowStart = Math.max(0, tableIndex - 600);
|
|
125105
|
+
const windowEnd = Math.min(html.length, tableIndex + tableHtml.length + 400);
|
|
125106
|
+
const nearby = html.slice(windowStart, windowEnd);
|
|
125107
|
+
return /对比|竞品|竞争|功能|性能|对标/.test(nearby);
|
|
125108
|
+
}
|
|
125109
|
+
function findFeatureCompareTableRows(html) {
|
|
125110
|
+
const tables = html.match(/<table[\s\S]*?<\/table>/gi) ?? [];
|
|
125111
|
+
let best = 0;
|
|
125112
|
+
let searchFrom = 0;
|
|
125113
|
+
for (const t of tables) {
|
|
125114
|
+
const tableIndex = html.indexOf(t, searchFrom);
|
|
125115
|
+
searchFrom = tableIndex + 1;
|
|
125116
|
+
if (countTableColumns(t) < 4) continue;
|
|
125117
|
+
if (!isCompareTable(t, html, tableIndex)) continue;
|
|
125118
|
+
const rows = (t.match(/<tr/gi) ?? []).length;
|
|
125119
|
+
if (rows > best) best = rows;
|
|
125120
|
+
}
|
|
125121
|
+
return best;
|
|
125122
|
+
}
|
|
125123
|
+
function countPainPoints(html) {
|
|
125124
|
+
const labeled = (html.match(/痛点\s*\d/gi) ?? []).length;
|
|
125125
|
+
if (labeled >= 4) return labeled;
|
|
125126
|
+
const section = html.match(/痛点[\s\S]{0,3000}/i)?.[0] ?? html;
|
|
125127
|
+
const listItems = (section.match(/<li[^>]*>/gi) ?? []).length;
|
|
125128
|
+
return Math.max(labeled, listItems >= 4 ? 4 : listItems);
|
|
125129
|
+
}
|
|
125021
125130
|
function countEchartsInstances(html) {
|
|
125022
125131
|
const inits = (html.match(/echarts\.init\(/g) ?? []).length;
|
|
125023
125132
|
if (inits > 0) return inits;
|
|
@@ -125026,67 +125135,97 @@ function countEchartsInstances(html) {
|
|
|
125026
125135
|
function hasEchartsCdn(html) {
|
|
125027
125136
|
return /homeCDN\/echarts\.js|echarts\.min\.js|npm\/echarts@/i.test(html);
|
|
125028
125137
|
}
|
|
125029
|
-
function
|
|
125030
|
-
const
|
|
125031
|
-
if (
|
|
125032
|
-
|
|
125033
|
-
|
|
125034
|
-
|
|
125035
|
-
|
|
125036
|
-
|
|
125037
|
-
|
|
125038
|
-
|
|
125039
|
-
|
|
125040
|
-
|
|
125041
|
-
const
|
|
125042
|
-
|
|
125043
|
-
|
|
125044
|
-
|
|
125045
|
-
|
|
125046
|
-
|
|
125047
|
-
|
|
125048
|
-
return best;
|
|
125049
|
-
}
|
|
125050
|
-
function mentionsCompetitors(html) {
|
|
125051
|
-
const brands = ["Apple", "Samsung", "Google", "\u82F9\u679C", "\u4E09\u661F", "\u8C37\u6B4C", "\u534E\u4E3A", "Motorola"];
|
|
125052
|
-
let n = 0;
|
|
125053
|
-
for (const b of brands) {
|
|
125054
|
-
if (html.includes(b)) n++;
|
|
125138
|
+
function countCompetitorMentions(html) {
|
|
125139
|
+
const competitorSection = html.match(/(?:主要)?竞品[\s\S]{0,5000}/i)?.[0] ?? html.match(/竞争对手[\s\S]{0,5000}/i)?.[0] ?? html.match(/对标(?:企业|品牌|公司)[\s\S]{0,5000}/i)?.[0] ?? "";
|
|
125140
|
+
if (competitorSection) {
|
|
125141
|
+
const listItems = (competitorSection.match(/<li[^>]*>[\s\S]*?<\/li>/gi) ?? []).filter(
|
|
125142
|
+
(li) => stripHtmlTags(li).trim().length >= 2
|
|
125143
|
+
);
|
|
125144
|
+
if (listItems.length >= 3) return listItems.length;
|
|
125145
|
+
const inlineList = competitorSection.match(/(?:竞品|竞争对手|对标)[::][\s\S]{0,800}/i)?.[0];
|
|
125146
|
+
if (inlineList) {
|
|
125147
|
+
const parts = stripHtmlTags(inlineList).split(/[、,,;;|]/).map((s) => s.replace(/^(竞品|竞争对手|对标)[::]/, "").trim()).filter((s) => s.length >= 2);
|
|
125148
|
+
if (parts.length >= 3) return parts.length;
|
|
125149
|
+
}
|
|
125150
|
+
const plain = stripHtmlTags(competitorSection);
|
|
125151
|
+
const entities = plain.match(
|
|
125152
|
+
/[\u4e00-\u9fa5A-Za-z0-9&][\u4e00-\u9fa5A-Za-z0-9&.\- ]{1,30}(?:公司|集团|Co\.|Corp|Inc|Ltd|品牌)?/g
|
|
125153
|
+
);
|
|
125154
|
+
if (entities && new Set(entities.map((e) => e.trim())).size >= 3) {
|
|
125155
|
+
return new Set(entities.map((e) => e.trim())).size;
|
|
125156
|
+
}
|
|
125055
125157
|
}
|
|
125056
|
-
|
|
125158
|
+
const tables = html.match(/<table[\s\S]*?<\/table>/gi) ?? [];
|
|
125159
|
+
let searchFrom = 0;
|
|
125160
|
+
for (const t of tables) {
|
|
125161
|
+
const tableIndex = html.indexOf(t, searchFrom);
|
|
125162
|
+
searchFrom = tableIndex + 1;
|
|
125163
|
+
const cols = countTableColumns(t);
|
|
125164
|
+
if (cols >= 4 && isCompareTable(t, html, tableIndex)) {
|
|
125165
|
+
return Math.max(3, cols - 1);
|
|
125166
|
+
}
|
|
125167
|
+
}
|
|
125168
|
+
const globalPlain = stripHtmlTags(html);
|
|
125169
|
+
const named = globalPlain.match(
|
|
125170
|
+
/(?:竞品|竞争对手)[::][^\n。;;]{10,400}/
|
|
125171
|
+
)?.[0] ?? "";
|
|
125172
|
+
if (named) {
|
|
125173
|
+
const parts = named.replace(/^[^::]+[::]/, "").split(/[、,,;;|]/).map((s) => s.trim()).filter((s) => s.length >= 2);
|
|
125174
|
+
if (parts.length >= 3) return parts.length;
|
|
125175
|
+
}
|
|
125176
|
+
return 0;
|
|
125177
|
+
}
|
|
125178
|
+
function splitMarketTokens(value) {
|
|
125179
|
+
return value.split(/[、,,/|]/).map((t) => t.trim()).filter((t) => t.length >= 2);
|
|
125180
|
+
}
|
|
125181
|
+
function hasTargetGeoBreakdown(html, ctx) {
|
|
125182
|
+
const hasMetrics = /规模|增长|优先级|潜力|份额|CAGR|市场容量/.test(html);
|
|
125183
|
+
const hasGeoFrame = /国家|区域|地区|市场/.test(html);
|
|
125184
|
+
if (!hasMetrics || !hasGeoFrame) return false;
|
|
125185
|
+
const targetMarket = ctx.targetMarket?.trim() ?? "";
|
|
125186
|
+
if (targetMarket) {
|
|
125187
|
+
if (/全球|国际|worldwide/i.test(targetMarket)) {
|
|
125188
|
+
return /全球|国际|各区域|分区域|分市场/.test(html);
|
|
125189
|
+
}
|
|
125190
|
+
const tokens = splitMarketTokens(targetMarket);
|
|
125191
|
+
const hit = tokens.filter((t) => html.includes(t)).length;
|
|
125192
|
+
if (hit >= 1) return true;
|
|
125193
|
+
}
|
|
125194
|
+
const regionHits = (html.match(
|
|
125195
|
+
/(?:亚太|欧洲|北美|拉美|中东|非洲|东南亚|全球|[\u4e00-\u9fa5]{2,8}(?:市场|地区|国家|区域))/g
|
|
125196
|
+
) ?? []).length;
|
|
125197
|
+
return regionHits >= 2;
|
|
125198
|
+
}
|
|
125199
|
+
function hasReportTitle(html, ctx) {
|
|
125200
|
+
const hasTitle = /战略市场分析|KA客户|行业分析报告|市场分析报告|行业调查|市场调查/.test(html);
|
|
125201
|
+
if (!hasTitle) return false;
|
|
125202
|
+
return /目标市场|全球市场|行业|细分|market/i.test(html) || !!ctx.targetMarket?.trim() || !!ctx.industry?.trim() || !!ctx.customerName?.trim();
|
|
125057
125203
|
}
|
|
125058
125204
|
var CONTENT_RULES = [
|
|
125059
|
-
{
|
|
125060
|
-
|
|
125061
|
-
test: (h) => /战略市场分析|KA客户/.test(h) && (/目标市场|北美|全球|欧洲|亚太/.test(h) || /targetMarket/i.test(h))
|
|
125062
|
-
},
|
|
125063
|
-
{ id: "cover-kpi", test: (h) => /核心结论|metric|kpi|结论/.test(h) },
|
|
125205
|
+
{ id: "cover", test: (h, ctx) => hasReportTitle(h, ctx) },
|
|
125206
|
+
{ id: "cover-kpi", test: (h) => /核心结论|metric|kpi|结论|摘要|executive summary/i.test(h) },
|
|
125064
125207
|
{ id: "ch1", test: (h) => /市场与趋势诊断/.test(h) },
|
|
125065
125208
|
{
|
|
125066
125209
|
id: "ch1-tam",
|
|
125067
125210
|
test: (h) => (/TAM|SAM|SOM/.test(h) || /市场规模/.test(h)) && /CAGR|增长率|增长趋势/.test(h)
|
|
125068
125211
|
},
|
|
125069
125212
|
{ id: "ch1-value-chain", test: (h) => /价值链|生态定位|产业链/.test(h) },
|
|
125070
|
-
{
|
|
125071
|
-
|
|
125072
|
-
test: (h) => (/美国|加拿大|墨西哥|中国|欧洲/.test(h) || /国家|区域/.test(h)) && /增长|规模|优先级|潜力/.test(h)
|
|
125073
|
-
},
|
|
125074
|
-
{ id: "ch1-pestel", test: (h) => /PESTEL|政策|法规环境|技术趋势/.test(h) },
|
|
125213
|
+
{ id: "ch1-target-geo", test: (h, ctx) => hasTargetGeoBreakdown(h, ctx) },
|
|
125214
|
+
{ id: "ch1-pestel", test: (h) => /PESTEL|政策|法规环境|技术趋势|经济|社会|环境/.test(h) },
|
|
125075
125215
|
{ id: "ch1-forecast", test: (h) => /预测|3.?5年|未来\d/.test(h) },
|
|
125076
125216
|
{ id: "ch2", test: (h) => /行业深度洞察|目标行业/.test(h) },
|
|
125077
125217
|
{ id: "ch2-pain", test: (h, ctx) => ctx.painPointCount >= 4 },
|
|
125078
|
-
{ id: "ch2-compliance", test: (h) =>
|
|
125218
|
+
{ id: "ch2-compliance", test: (h) => /法规|合规/.test(h) && /<table/.test(h) },
|
|
125079
125219
|
{
|
|
125080
125220
|
id: "ch2-bpmn",
|
|
125081
|
-
test: (h) => /原流程|BPMN|介入后|新流程/.test(h) &&
|
|
125221
|
+
test: (h) => /原流程|BPMN|介入后|新流程/.test(h) && /效率|成本|提升|下降|节约/.test(h)
|
|
125082
125222
|
},
|
|
125083
125223
|
{ id: "ch3", test: (h) => /目标受众|受众与场景/.test(h) },
|
|
125084
125224
|
{ id: "ch3-audience", test: (h) => /用户|受众|画像|群体/.test(h) && /<table/.test(h) },
|
|
125085
125225
|
{ id: "ch3-scenario", test: (h) => /应用场景|场景|差异化|用户价值|企业价值/.test(h) },
|
|
125086
125226
|
{ id: "ch4", test: (h) => /竞争策略|竞争.{0,6}定位/.test(h) },
|
|
125087
|
-
{ id: "ch4-competitors", test: (h) =>
|
|
125088
|
-
|
|
125089
|
-
{ id: "ch4-compare-table", test: (h) => competitorCompareTableRows(h) >= 11 },
|
|
125227
|
+
{ id: "ch4-competitors", test: (h, ctx) => ctx.competitorCount >= 3 },
|
|
125228
|
+
{ id: "ch4-compare-table", test: (h, ctx) => ctx.competitorTableRows >= 11 },
|
|
125090
125229
|
{
|
|
125091
125230
|
id: "ch4-gaps",
|
|
125092
125231
|
test: (h) => [
|
|
@@ -125115,11 +125254,22 @@ var CONTENT_RULES = [
|
|
|
125115
125254
|
{ id: "viz-echarts", test: (h, ctx) => hasEchartsCdn(h) && ctx.chartCount >= 1 },
|
|
125116
125255
|
{ id: "viz-source", test: (h) => /Source:|来源:|来源:/.test(h) }
|
|
125117
125256
|
];
|
|
125118
|
-
function
|
|
125257
|
+
function validationContextFromReport(data) {
|
|
125258
|
+
const customerInfo = data.customerInfo && typeof data.customerInfo === "object" && !Array.isArray(data.customerInfo) ? data.customerInfo : {};
|
|
125259
|
+
return {
|
|
125260
|
+
targetMarket: String(data.targetMarket ?? customerInfo.targetMarket ?? "").trim() || void 0,
|
|
125261
|
+
industry: String(customerInfo.industry ?? "").trim() || void 0,
|
|
125262
|
+
customerName: String(customerInfo.customerName ?? "").trim() || void 0
|
|
125263
|
+
};
|
|
125264
|
+
}
|
|
125265
|
+
function validateMarketReportContent(html, reportContext) {
|
|
125119
125266
|
const ctx = {
|
|
125120
|
-
|
|
125267
|
+
...reportContext,
|
|
125268
|
+
tableCount: (html.match(/<table/gi) ?? []).length,
|
|
125121
125269
|
chartCount: countEchartsInstances(html),
|
|
125122
|
-
painPointCount: countPainPoints(html)
|
|
125270
|
+
painPointCount: countPainPoints(html),
|
|
125271
|
+
competitorTableRows: findFeatureCompareTableRows(html),
|
|
125272
|
+
competitorCount: countCompetitorMentions(html)
|
|
125123
125273
|
};
|
|
125124
125274
|
const dimById = new Map(MARKET_REPORT_DIMENSIONS.map((d) => [d.id, d]));
|
|
125125
125275
|
const missing = [];
|
|
@@ -125142,7 +125292,7 @@ function validateMarketReportContent(html) {
|
|
|
125142
125292
|
tables: ctx.tableCount,
|
|
125143
125293
|
charts: ctx.chartCount,
|
|
125144
125294
|
painPoints: ctx.painPointCount,
|
|
125145
|
-
competitorTableRows:
|
|
125295
|
+
competitorTableRows: ctx.competitorTableRows
|
|
125146
125296
|
}
|
|
125147
125297
|
};
|
|
125148
125298
|
}
|
|
@@ -125150,7 +125300,7 @@ function formatMarketReportContentErrors(result) {
|
|
|
125150
125300
|
if (result.ok) return "";
|
|
125151
125301
|
const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
|
|
125152
125302
|
return [
|
|
125153
|
-
`\u62A5\u544A\u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\
|
|
125303
|
+
`\u62A5\u544A\u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u901A\u7528\u884C\u4E1A\u8C03\u67E5\u7EF4\u5EA6\uFF09\uFF1A`,
|
|
125154
125304
|
...lines,
|
|
125155
125305
|
"",
|
|
125156
125306
|
"\u8BF7 Read assets/market-analysis-rules.md\u300C\u539F\u59CB\u4E1A\u52A1\u7EF4\u5EA6\u6E05\u5355\u300D\uFF0CWebSearch \u8865\u5168\u7F3A\u5931\u7AE0\u8282\u540E\u91CD\u5199 market-report.json\u3002"
|
|
@@ -125200,7 +125350,8 @@ async function runMarketAnalysisRender(opts) {
|
|
|
125200
125350
|
console.error("\n\u274C \u62A5\u544A HTML \u5185\u5BB9\u4E3A\u7A7A\n");
|
|
125201
125351
|
process.exit(1);
|
|
125202
125352
|
}
|
|
125203
|
-
const
|
|
125353
|
+
const reportData = asRecord4(dataRaw);
|
|
125354
|
+
const contentCheck = validateMarketReportContent(html, validationContextFromReport(reportData));
|
|
125204
125355
|
if (!contentCheck.ok) {
|
|
125205
125356
|
console.error(`
|
|
125206
125357
|
\u274C ${formatMarketReportContentErrors(contentCheck)}
|
|
@@ -126276,43 +126427,51 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
126276
126427
|
}
|
|
126277
126428
|
const snapDir = path22.resolve(opts.jsonOut);
|
|
126278
126429
|
await fs17.mkdir(snapDir, { recursive: true });
|
|
126279
|
-
|
|
126280
|
-
|
|
126430
|
+
let degraded = false;
|
|
126431
|
+
console.error(`
|
|
126281
126432
|
\u{1F4E5} google-analysis \u62C9\u6570\uFF1A\u8D26\u6237 ${accountId}\uFF0C${opts.start} ~ ${opts.end}
|
|
126282
126433
|
`);
|
|
126283
|
-
|
|
126434
|
+
const main = invokeGoogleAnalysisCli({
|
|
126435
|
+
account: accountId,
|
|
126436
|
+
start: opts.start,
|
|
126437
|
+
end: opts.end,
|
|
126438
|
+
jsonOut: snapDir,
|
|
126439
|
+
token: opts.token,
|
|
126440
|
+
verbose: opts.verbose
|
|
126441
|
+
});
|
|
126442
|
+
const mainPartial = !main.ok && main.status === 2;
|
|
126443
|
+
if (!main.ok && !mainPartial) {
|
|
126444
|
+
console.error(main.stderr || main.stdout);
|
|
126445
|
+
console.error("\n\u274C google-analysis \u62C9\u6570\u5931\u8D25\u3002\n");
|
|
126446
|
+
process.exit(main.status || 1);
|
|
126447
|
+
}
|
|
126448
|
+
if (mainPartial) {
|
|
126449
|
+
console.error(main.stderr || main.stdout);
|
|
126450
|
+
console.error(
|
|
126451
|
+
"\n\u26A0\uFE0F google-analysis \u90E8\u5206\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u5C06\u7EE7\u7EED\u62C9\u53D6\u5BF9\u6BD4\u5468\u671F\u5E76\u751F\u6210 collect\u3002\n"
|
|
126452
|
+
);
|
|
126453
|
+
degraded = true;
|
|
126454
|
+
}
|
|
126455
|
+
if (opts.fetchPreviousPeriod !== false) {
|
|
126456
|
+
const prev = previousDateRange2(opts.start, opts.end);
|
|
126457
|
+
console.error(
|
|
126458
|
+
`
|
|
126459
|
+
\u{1F4E5} \u62C9\u53D6\u5BF9\u6BD4\u5468\u671F campaigns/geographic/keywords\uFF1A${prev.start} ~ ${prev.end}
|
|
126460
|
+
`
|
|
126461
|
+
);
|
|
126462
|
+
const prevRun = invokeGoogleAnalysisCli({
|
|
126284
126463
|
account: accountId,
|
|
126285
|
-
start:
|
|
126286
|
-
end:
|
|
126464
|
+
start: prev.start,
|
|
126465
|
+
end: prev.end,
|
|
126287
126466
|
jsonOut: snapDir,
|
|
126467
|
+
sections: "campaigns,geographic,keywords",
|
|
126288
126468
|
token: opts.token,
|
|
126289
126469
|
verbose: opts.verbose
|
|
126290
126470
|
});
|
|
126291
|
-
if (!
|
|
126292
|
-
console.error(
|
|
126293
|
-
console.error("\n\
|
|
126294
|
-
|
|
126295
|
-
}
|
|
126296
|
-
if (opts.fetchPreviousPeriod !== false) {
|
|
126297
|
-
const prev = previousDateRange2(opts.start, opts.end);
|
|
126298
|
-
console.error(
|
|
126299
|
-
`
|
|
126300
|
-
\u{1F4E5} \u62C9\u53D6\u5BF9\u6BD4\u5468\u671F campaigns/geographic/keywords\uFF1A${prev.start} ~ ${prev.end}
|
|
126301
|
-
`
|
|
126302
|
-
);
|
|
126303
|
-
const prevRun = invokeGoogleAnalysisCli({
|
|
126304
|
-
account: accountId,
|
|
126305
|
-
start: prev.start,
|
|
126306
|
-
end: prev.end,
|
|
126307
|
-
jsonOut: snapDir,
|
|
126308
|
-
sections: "campaigns,geographic,keywords",
|
|
126309
|
-
token: opts.token,
|
|
126310
|
-
verbose: opts.verbose
|
|
126311
|
-
});
|
|
126312
|
-
if (!prevRun.ok) {
|
|
126313
|
-
console.error(prevRun.stderr || prevRun.stdout);
|
|
126314
|
-
console.error("\n\u26A0\uFE0F \u4E0A\u4E00\u5468\u671F\u5BF9\u6BD4\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u73AF\u6BD4\u5217\u53EF\u80FD\u4E3A\u7A7A\u3002\n");
|
|
126315
|
-
}
|
|
126471
|
+
if (!prevRun.ok) {
|
|
126472
|
+
console.error(prevRun.stderr || prevRun.stdout);
|
|
126473
|
+
console.error("\n\u26A0\uFE0F \u4E0A\u4E00\u5468\u671F\u5BF9\u6BD4\u7EF4\u5EA6\u62C9\u53D6\u5931\u8D25\uFF0C\u73AF\u6BD4\u5217\u53EF\u80FD\u4E3A\u7A7A\u3002\n");
|
|
126474
|
+
degraded = true;
|
|
126316
126475
|
}
|
|
126317
126476
|
}
|
|
126318
126477
|
const websiteUrl = await resolveWebsiteUrl(opts, snapDir, accountId);
|
|
@@ -126364,6 +126523,7 @@ async function runGoogleAdsDiagnosisCollect(opts) {
|
|
|
126364
126523
|
` \u7EC8\u7A3F\uFF1Asiluzan-tso google-ads-diagnosis render --data ${path22.join(snapDir, GOOGLE_ADS_DIAGNOSIS_REPORT_BASENAME)}
|
|
126365
126524
|
`
|
|
126366
126525
|
);
|
|
126526
|
+
if (degraded) process.exit(2);
|
|
126367
126527
|
}
|
|
126368
126528
|
|
|
126369
126529
|
// src/commands/google-ads-diagnosis/render-report.ts
|
|
@@ -126707,7 +126867,7 @@ function registerGoogleAdsDiagnosisCommands(program2) {
|
|
|
126707
126867
|
).requiredOption("-a, --account <id>", "Google mediaCustomerId").requiredOption("--start <date>", "\u8BCA\u65AD\u5F00\u59CB\u65E5\u671F YYYY-MM-DD").requiredOption("--end <date>", "\u8BCA\u65AD\u7ED3\u675F\u65E5\u671F YYYY-MM-DD").option("--company-name <name>", "\u8D26\u6237\u540D\u79F0\uFF08\u9ED8\u8BA4\u4ECE list-accounts \u5FEB\u7167\u8BFB\u53D6\uFF09").option("--website <url>", "\u5B98\u7F51 URL\uFF08\u9ED8\u8BA4\u4ECE ads \u5FEB\u7167 finalurl \u8BFB\u53D6\uFF09").option(
|
|
126708
126868
|
"--json-out <dir>",
|
|
126709
126869
|
"\u843D\u76D8\u76EE\u5F55\uFF08google-analysis \u5FEB\u7167 + google-ads-diagnosis-collect.json\uFF09"
|
|
126710
|
-
).option("--skip-
|
|
126870
|
+
).option("--skip-landing-page", "\u4E0D\u62C9\u53D6 WebsiteDiagnosisReports/performance").option("--no-fetch-previous-period", "\u4E0D\u62C9\u4E0A\u4E00\u5468\u671F campaigns/geographic/keywords \u5BF9\u6BD4\u6570\u636E").option("--token <token>", "JWT").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").action(
|
|
126711
126871
|
async (opts) => {
|
|
126712
126872
|
if (!opts.jsonOut?.trim()) {
|
|
126713
126873
|
console.error("\n\u274C collect \u987B\u6307\u5B9A --json-out <\u76EE\u5F55>\u3002\n");
|
|
@@ -126720,7 +126880,6 @@ function registerGoogleAdsDiagnosisCommands(program2) {
|
|
|
126720
126880
|
companyName: opts.companyName,
|
|
126721
126881
|
website: opts.website,
|
|
126722
126882
|
jsonOut: opts.jsonOut,
|
|
126723
|
-
skipFetch: opts.skipFetch,
|
|
126724
126883
|
skipLandingPage: opts.skipLandingPage,
|
|
126725
126884
|
fetchPreviousPeriod: opts.fetchPreviousPeriod,
|
|
126726
126885
|
token: opts.token,
|