@canonry/canonry 4.147.0 → 4.148.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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -2
- package/assets/assets/{AuditHistoryPanel-DiQ0Ynw5.js → AuditHistoryPanel-D5fRv5sF.js} +1 -1
- package/assets/assets/{BacklinksPage-DLUQuPE_.js → BacklinksPage-CFdO1s1V.js} +1 -1
- package/assets/assets/{ChartPrimitives-BoEy1qo7.js → ChartPrimitives-BoB1RfNf.js} +1 -1
- package/assets/assets/{HistoryPage-mYR2vk8n.js → HistoryPage-YT2JCo2G.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BocyGHJN.js → MeasurementPropertyPage-JDEzHX1H.js} +1 -1
- package/assets/assets/ProjectPage-B6t_j9tN.js +12 -0
- package/assets/assets/{RunRow-Ct-A6H9e.js → RunRow-yKrIU8zY.js} +1 -1
- package/assets/assets/{RunsPage-CsDtq0Qw.js → RunsPage-DAeC6Ebm.js} +1 -1
- package/assets/assets/{SettingsPage-BXwFGc1P.js → SettingsPage-8Z251PH0.js} +1 -1
- package/assets/assets/{TrafficPage-B2omyoI8.js → TrafficPage-DNJGp16_.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CoEqc6OJ.js → TrafficSourceDetailPage-BuzdNFB8.js} +1 -1
- package/assets/assets/{arrow-left-Dh0vsQxe.js → arrow-left-BrVL2J2e.js} +1 -1
- package/assets/assets/{extract-error-message-DvWJ0xRn.js → extract-error-message-jd9FdYvE.js} +1 -1
- package/assets/assets/index-Bo0hzrG2.js +213 -0
- package/assets/assets/{index-CXe6S1ey.css → index-D5NO3NEG.css} +1 -1
- package/assets/assets/{trash-2-D1_8ZseY.js → trash-2-lAIhBm6w.js} +1 -1
- package/assets/assets/{v2-overview-adapter-owDeFdUc.js → v2-overview-adapter-BHmxTmCf.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-4BL24FQA.js → chunk-3HYF3LUP.js} +353 -346
- package/dist/{chunk-BVOU4UJM.js → chunk-43XIYBG4.js} +8 -0
- package/dist/{chunk-ZDYS24LW.js → chunk-MHTZ4THA.js} +7 -4
- package/dist/{chunk-DRDNHYIF.js → chunk-VUTKHFTM.js} +77 -22
- package/dist/cli.js +209 -7
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-7CGZ537D.js → intelligence-service-OGOGIQJD.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
- package/assets/assets/ProjectPage-Q5wQ7nSW.js +0 -12
- package/assets/assets/index-FE8JPHmT.js +0 -213
|
@@ -5436,6 +5436,14 @@ var apiKeyDtoSchema = z9.object({
|
|
|
5436
5436
|
* scoped key may only read/write its own project (enforced server-side).
|
|
5437
5437
|
*/
|
|
5438
5438
|
projectId: z9.string().nullable(),
|
|
5439
|
+
/**
|
|
5440
|
+
* The scoped project's name, or `null` for a full-instance key. Resolved
|
|
5441
|
+
* server-side so a surface listing keys does not have to fetch every project
|
|
5442
|
+
* to turn an id into something a person can read — the id alone cannot answer
|
|
5443
|
+
* "is this key safe to hand to someone", which is the question the list is
|
|
5444
|
+
* usually open to answer. Additive field.
|
|
5445
|
+
*/
|
|
5446
|
+
projectName: z9.string().nullable(),
|
|
5439
5447
|
/**
|
|
5440
5448
|
* Server-derived convenience flag: `true` when this key is read-only (carries
|
|
5441
5449
|
* the `read` scope and no write-granting scope), in which case the API rejects
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-3HYF3LUP.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -126,7 +126,7 @@ import {
|
|
|
126
126
|
siteAuditSnapshots,
|
|
127
127
|
toAlertView,
|
|
128
128
|
usageCounters
|
|
129
|
-
} from "./chunk-
|
|
129
|
+
} from "./chunk-VUTKHFTM.js";
|
|
130
130
|
import {
|
|
131
131
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
132
132
|
AGENT_PROVIDER_IDS,
|
|
@@ -217,7 +217,7 @@ import {
|
|
|
217
217
|
validationError,
|
|
218
218
|
winnabilityClassLabel,
|
|
219
219
|
withRetry
|
|
220
|
-
} from "./chunk-
|
|
220
|
+
} from "./chunk-43XIYBG4.js";
|
|
221
221
|
|
|
222
222
|
// src/telemetry.ts
|
|
223
223
|
import crypto from "crypto";
|
|
@@ -7420,7 +7420,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
7420
7420
|
return result;
|
|
7421
7421
|
}
|
|
7422
7422
|
async function backfillInsightsCommand(project, opts) {
|
|
7423
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
7423
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-OGOGIQJD.js");
|
|
7424
7424
|
const config = loadConfig();
|
|
7425
7425
|
const db = createClient(config.database);
|
|
7426
7426
|
migrate(db);
|
|
@@ -13885,6 +13885,9 @@ async function createServer(opts) {
|
|
|
13885
13885
|
if (!dashboardShowUpdateNotification) {
|
|
13886
13886
|
dashboardConfig.showUpdateNotification = false;
|
|
13887
13887
|
}
|
|
13888
|
+
if (!agentEnabled) {
|
|
13889
|
+
dashboardConfig.showAgentBar = false;
|
|
13890
|
+
}
|
|
13888
13891
|
if (Object.keys(dashboardConfig).length > 0) {
|
|
13889
13892
|
clientConfig.dashboard = dashboardConfig;
|
|
13890
13893
|
}
|
|
@@ -517,7 +517,7 @@ import {
|
|
|
517
517
|
wordpressSchemaDeployResultDtoSchema,
|
|
518
518
|
wordpressSchemaStatusResultDtoSchema,
|
|
519
519
|
wordpressStatusDtoSchema
|
|
520
|
-
} from "./chunk-
|
|
520
|
+
} from "./chunk-43XIYBG4.js";
|
|
521
521
|
|
|
522
522
|
// src/intelligence-service.ts
|
|
523
523
|
import { eq as eq57, desc as desc26, asc as asc11, and as and45, ne as ne7, or as or11, inArray as inArray19, gte as gte13, lte as lte10 } from "drizzle-orm";
|
|
@@ -5037,6 +5037,20 @@ var MIGRATION_VERSIONS = [
|
|
|
5037
5037
|
`CREATE INDEX IF NOT EXISTS idx_measurement_operation_receipts_expires
|
|
5038
5038
|
ON measurement_operation_receipts(expires_at)`
|
|
5039
5039
|
]
|
|
5040
|
+
},
|
|
5041
|
+
{
|
|
5042
|
+
version: 124,
|
|
5043
|
+
name: "measurement-foundation-named-indexes",
|
|
5044
|
+
statements: [
|
|
5045
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_plan_versions_project_revision
|
|
5046
|
+
ON measurement_plan_versions(project_id, revision)`,
|
|
5047
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_plan_versions_project_id
|
|
5048
|
+
ON measurement_plan_versions(project_id, id)`,
|
|
5049
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_segments_project_key
|
|
5050
|
+
ON measurement_segments(project_id, stable_key)`,
|
|
5051
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_measurement_segments_project_id
|
|
5052
|
+
ON measurement_segments(project_id, id)`
|
|
5053
|
+
]
|
|
5040
5054
|
}
|
|
5041
5055
|
];
|
|
5042
5056
|
function addRunsMeasurementPlanVersionForeignKey(tx) {
|
|
@@ -32713,26 +32727,32 @@ function buildOperationId(method, path8) {
|
|
|
32713
32727
|
import crypto22 from "crypto";
|
|
32714
32728
|
import { desc as desc16, eq as eq35 } from "drizzle-orm";
|
|
32715
32729
|
var KEYS_WRITE_SCOPE = "keys.write";
|
|
32716
|
-
function toApiKeyDto(row) {
|
|
32730
|
+
function toApiKeyDto(row, projectNames) {
|
|
32717
32731
|
const scopes = Array.isArray(row.scopes) ? row.scopes : [];
|
|
32732
|
+
const projectId = row.projectId ?? null;
|
|
32718
32733
|
return {
|
|
32719
32734
|
id: row.id,
|
|
32720
32735
|
name: row.name,
|
|
32721
32736
|
keyPrefix: row.keyPrefix,
|
|
32722
32737
|
scopes,
|
|
32723
|
-
projectId
|
|
32738
|
+
projectId,
|
|
32739
|
+
projectName: projectId ? projectNames.get(projectId) ?? null : null,
|
|
32724
32740
|
readOnly: isReadOnlyKey(scopes),
|
|
32725
32741
|
createdAt: row.createdAt,
|
|
32726
32742
|
lastUsedAt: row.lastUsedAt ?? null,
|
|
32727
32743
|
revokedAt: row.revokedAt ?? null
|
|
32728
32744
|
};
|
|
32729
32745
|
}
|
|
32746
|
+
function projectNameById(db) {
|
|
32747
|
+
return new Map(db.select().from(projects).all().map((p) => [p.id, p.name]));
|
|
32748
|
+
}
|
|
32730
32749
|
async function keysRoutes(app) {
|
|
32731
32750
|
app.get("/keys", async (request) => {
|
|
32732
32751
|
requireAdminSession(request);
|
|
32733
32752
|
const scopedProjectId = request.apiKey?.projectId;
|
|
32734
32753
|
const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq35(apiKeys.projectId, scopedProjectId)).orderBy(desc16(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc16(apiKeys.createdAt)).all();
|
|
32735
|
-
|
|
32754
|
+
const names = projectNameById(app.db);
|
|
32755
|
+
return { keys: rows.map((row) => toApiKeyDto(row, names)) };
|
|
32736
32756
|
});
|
|
32737
32757
|
app.get("/keys/self", async (request) => {
|
|
32738
32758
|
const id = request.apiKey?.id;
|
|
@@ -32743,7 +32763,7 @@ async function keysRoutes(app) {
|
|
|
32743
32763
|
if (!row) {
|
|
32744
32764
|
throw notFound("API key", id);
|
|
32745
32765
|
}
|
|
32746
|
-
return toApiKeyDto(row);
|
|
32766
|
+
return toApiKeyDto(row, projectNameById(app.db));
|
|
32747
32767
|
});
|
|
32748
32768
|
app.post("/keys", async (request) => {
|
|
32749
32769
|
requireAdminSession(request);
|
|
@@ -32793,6 +32813,9 @@ async function keysRoutes(app) {
|
|
|
32793
32813
|
keyPrefix,
|
|
32794
32814
|
scopes: effectiveScopes,
|
|
32795
32815
|
projectId: projectId ?? null,
|
|
32816
|
+
// Resolved from the project the caller just named, so the response says
|
|
32817
|
+
// what the key reaches without a second round trip.
|
|
32818
|
+
projectName: projectId ? projectNameById(app.db).get(projectId) ?? null : null,
|
|
32796
32819
|
readOnly: isReadOnlyKey(effectiveScopes),
|
|
32797
32820
|
createdAt: now,
|
|
32798
32821
|
lastUsedAt: null,
|
|
@@ -32817,7 +32840,7 @@ async function keysRoutes(app) {
|
|
|
32817
32840
|
throw forbidden("A project-scoped key can only revoke keys scoped to its own project.");
|
|
32818
32841
|
}
|
|
32819
32842
|
if (row.revokedAt) {
|
|
32820
|
-
return toApiKeyDto(row);
|
|
32843
|
+
return toApiKeyDto(row, projectNameById(app.db));
|
|
32821
32844
|
}
|
|
32822
32845
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
32823
32846
|
app.db.transaction((tx) => {
|
|
@@ -32830,7 +32853,7 @@ async function keysRoutes(app) {
|
|
|
32830
32853
|
diff: { name: row.name, keyPrefix: row.keyPrefix }
|
|
32831
32854
|
}));
|
|
32832
32855
|
});
|
|
32833
|
-
return toApiKeyDto({ ...row, revokedAt: now });
|
|
32856
|
+
return toApiKeyDto({ ...row, revokedAt: now }, projectNameById(app.db));
|
|
32834
32857
|
});
|
|
32835
32858
|
}
|
|
32836
32859
|
|
|
@@ -33795,7 +33818,8 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
|
|
|
33795
33818
|
detail += detail ? `: ${sanitized}` : sanitized;
|
|
33796
33819
|
}
|
|
33797
33820
|
} catch {
|
|
33798
|
-
|
|
33821
|
+
const truncated = body.length <= 120 ? body : `${body.slice(0, 120)}...`;
|
|
33822
|
+
detail = truncated.replace(new RegExp(escapeRegExp(clientId), "g"), "***").replace(new RegExp(escapeRegExp(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp(code), "g"), "***");
|
|
33799
33823
|
}
|
|
33800
33824
|
throw new GoogleAuthError(`Token exchange failed (${res.status}): ${detail}`);
|
|
33801
33825
|
}
|
|
@@ -33833,7 +33857,8 @@ async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
|
|
|
33833
33857
|
detail += detail ? `: ${sanitized}` : sanitized;
|
|
33834
33858
|
}
|
|
33835
33859
|
} catch {
|
|
33836
|
-
|
|
33860
|
+
const truncated = body.length <= 120 ? body : `${body.slice(0, 120)}...`;
|
|
33861
|
+
detail = truncated.replace(new RegExp(escapeRegExp(clientId), "g"), "***").replace(new RegExp(escapeRegExp(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp(currentRefreshToken), "g"), "***");
|
|
33837
33862
|
}
|
|
33838
33863
|
throw new GoogleAuthError(`Token refresh failed (${res.status}): ${detail}`);
|
|
33839
33864
|
}
|
|
@@ -33923,6 +33948,9 @@ function gscClientLog(level, action, ctx) {
|
|
|
33923
33948
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
33924
33949
|
stream.write(JSON.stringify(entry) + "\n");
|
|
33925
33950
|
}
|
|
33951
|
+
function escapeRegExp2(str) {
|
|
33952
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33953
|
+
}
|
|
33926
33954
|
async function gscFetchResponse(accessToken, url, opts) {
|
|
33927
33955
|
const method = opts?.method ?? "GET";
|
|
33928
33956
|
const headers = {
|
|
@@ -33947,7 +33975,11 @@ async function gscFetchResponse(accessToken, url, opts) {
|
|
|
33947
33975
|
const body = await res.text();
|
|
33948
33976
|
gscClientLog("error", "http.error", { url, method, httpStatus: res.status });
|
|
33949
33977
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
33950
|
-
|
|
33978
|
+
let sanitizedDetail = detail;
|
|
33979
|
+
if (accessToken) {
|
|
33980
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp2(accessToken), "g"), "***");
|
|
33981
|
+
}
|
|
33982
|
+
throw new GoogleApiError(`GSC API error (${res.status}): ${sanitizedDetail}`, res.status);
|
|
33951
33983
|
}
|
|
33952
33984
|
return res;
|
|
33953
33985
|
}
|
|
@@ -34206,13 +34238,13 @@ async function getAccessToken(clientEmail, privateKey) {
|
|
|
34206
34238
|
const body = await res.text().catch(() => "");
|
|
34207
34239
|
ga4Log("error", "token.failed", { httpStatus: res.status });
|
|
34208
34240
|
const detail = body.length <= 200 ? body : `${body.slice(0, 200)}... [truncated]`;
|
|
34209
|
-
const sanitizedDetail = detail.replace(new RegExp(
|
|
34241
|
+
const sanitizedDetail = detail.replace(new RegExp(escapeRegExp3(clientEmail), "g"), "***").replace(new RegExp(escapeRegExp3(privateKey.slice(0, 32)), "g"), "***");
|
|
34210
34242
|
throw new GA4ApiError(`Failed to get access token: ${sanitizedDetail}`, res.status);
|
|
34211
34243
|
}
|
|
34212
34244
|
const data = await res.json();
|
|
34213
34245
|
return data.access_token;
|
|
34214
34246
|
}
|
|
34215
|
-
function
|
|
34247
|
+
function escapeRegExp3(str) {
|
|
34216
34248
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34217
34249
|
}
|
|
34218
34250
|
var gaInFlight = 0;
|
|
@@ -34315,9 +34347,13 @@ async function runReport(accessToken, propertyId, request) {
|
|
|
34315
34347
|
} catch {
|
|
34316
34348
|
if (body.length < 200) detail = ` ${body}`;
|
|
34317
34349
|
}
|
|
34350
|
+
let sanitizedDetail = detail;
|
|
34351
|
+
if (accessToken) {
|
|
34352
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp3(accessToken), "g"), "***");
|
|
34353
|
+
}
|
|
34318
34354
|
ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status });
|
|
34319
34355
|
throw new GA4ApiError(
|
|
34320
|
-
`GA4 API authentication failed \u2014 check service account permissions.${
|
|
34356
|
+
`GA4 API authentication failed \u2014 check service account permissions.${sanitizedDetail}`,
|
|
34321
34357
|
res.status
|
|
34322
34358
|
);
|
|
34323
34359
|
}
|
|
@@ -34331,7 +34367,11 @@ async function runReport(accessToken, propertyId, request) {
|
|
|
34331
34367
|
const retryAfterSeconds = res.status >= 500 ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
|
|
34332
34368
|
ga4Log("error", "report.error", { propertyId, httpStatus: res.status, retryAfterSeconds });
|
|
34333
34369
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
34334
|
-
|
|
34370
|
+
let sanitizedDetail = detail;
|
|
34371
|
+
if (accessToken) {
|
|
34372
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp3(accessToken), "g"), "***");
|
|
34373
|
+
}
|
|
34374
|
+
throw new GA4ApiError(`GA4 API error (${res.status}): ${sanitizedDetail}`, res.status, retryAfterSeconds);
|
|
34335
34375
|
}
|
|
34336
34376
|
return await res.json();
|
|
34337
34377
|
}, "report"));
|
|
@@ -34351,8 +34391,12 @@ async function batchRunReports(accessToken, propertyId, requests) {
|
|
|
34351
34391
|
if (res.status === 401 || res.status === 403) {
|
|
34352
34392
|
const body = await res.text().catch(() => "");
|
|
34353
34393
|
ga4Log("error", "batch-report.auth-failed", { propertyId, httpStatus: res.status });
|
|
34394
|
+
let sanitizedDetail = body;
|
|
34395
|
+
if (accessToken) {
|
|
34396
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp3(accessToken), "g"), "***");
|
|
34397
|
+
}
|
|
34354
34398
|
throw new GA4ApiError(
|
|
34355
|
-
`GA4 API authentication failed \u2014 check service account permissions. ${
|
|
34399
|
+
`GA4 API authentication failed \u2014 check service account permissions. ${sanitizedDetail}`,
|
|
34356
34400
|
res.status
|
|
34357
34401
|
);
|
|
34358
34402
|
}
|
|
@@ -34366,7 +34410,11 @@ async function batchRunReports(accessToken, propertyId, requests) {
|
|
|
34366
34410
|
const retryAfterSeconds = res.status >= 500 ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
|
|
34367
34411
|
ga4Log("error", "batch-report.error", { propertyId, httpStatus: res.status, retryAfterSeconds });
|
|
34368
34412
|
const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
34369
|
-
|
|
34413
|
+
let sanitizedDetail = detail;
|
|
34414
|
+
if (accessToken) {
|
|
34415
|
+
sanitizedDetail = sanitizedDetail.replace(new RegExp(escapeRegExp3(accessToken), "g"), "***");
|
|
34416
|
+
}
|
|
34417
|
+
throw new GA4ApiError(`GA4 API error (${res.status}): ${sanitizedDetail}`, res.status, retryAfterSeconds);
|
|
34370
34418
|
}
|
|
34371
34419
|
const data = await res.json();
|
|
34372
34420
|
return data.reports;
|
|
@@ -41701,7 +41749,7 @@ function bingClientLog(level, action, ctx) {
|
|
|
41701
41749
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
41702
41750
|
stream.write(JSON.stringify(entry) + "\n");
|
|
41703
41751
|
}
|
|
41704
|
-
function
|
|
41752
|
+
function escapeRegExp4(str) {
|
|
41705
41753
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41706
41754
|
}
|
|
41707
41755
|
async function bingFetch(apiKey, endpoint, opts) {
|
|
@@ -41729,7 +41777,7 @@ async function bingFetch(apiKey, endpoint, opts) {
|
|
|
41729
41777
|
const body = await res.text();
|
|
41730
41778
|
bingClientLog("error", "http.error", { endpoint, method, httpStatus: res.status });
|
|
41731
41779
|
let detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
|
|
41732
|
-
detail = detail.replace(new RegExp(
|
|
41780
|
+
detail = detail.replace(new RegExp(escapeRegExp4(apiKey), "g"), "***");
|
|
41733
41781
|
throw new BingApiError(`Bing API error (${res.status}): ${detail}`, res.status);
|
|
41734
41782
|
}
|
|
41735
41783
|
const text2 = await res.text();
|
|
@@ -43938,7 +43986,14 @@ async function fetchJson(connection, siteUrl, path8, init) {
|
|
|
43938
43986
|
}
|
|
43939
43987
|
if (!res.ok) {
|
|
43940
43988
|
const text2 = await res.text().catch(() => "");
|
|
43941
|
-
|
|
43989
|
+
let detail = text2 || res.statusText;
|
|
43990
|
+
if (connection.appPassword) {
|
|
43991
|
+
detail = detail.replace(new RegExp(escapeRegExp5(connection.appPassword), "g"), "***");
|
|
43992
|
+
}
|
|
43993
|
+
if (connection.username) {
|
|
43994
|
+
detail = detail.replace(new RegExp(escapeRegExp5(connection.username), "g"), "***");
|
|
43995
|
+
}
|
|
43996
|
+
throw new WordpressApiError("UPSTREAM_ERROR", `WordPress API error (${res.status}): ${detail}`, res.status);
|
|
43942
43997
|
}
|
|
43943
43998
|
return {
|
|
43944
43999
|
body: await res.json(),
|
|
@@ -44431,12 +44486,12 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
|
|
|
44431
44486
|
var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
|
|
44432
44487
|
function stripCanonrySchema(content) {
|
|
44433
44488
|
const regex = new RegExp(
|
|
44434
|
-
`${
|
|
44489
|
+
`${escapeRegExp5(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp5(CANONRY_SCHEMA_END)}`,
|
|
44435
44490
|
"g"
|
|
44436
44491
|
);
|
|
44437
44492
|
return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
44438
44493
|
}
|
|
44439
|
-
function
|
|
44494
|
+
function escapeRegExp5(str) {
|
|
44440
44495
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44441
44496
|
}
|
|
44442
44497
|
function injectCanonrySchema(content, schemas) {
|
|
@@ -44543,7 +44598,7 @@ async function getSchemaStatus(connection, env) {
|
|
|
44543
44598
|
const thirdPartySchemas = [];
|
|
44544
44599
|
if (hasCanonryMarker) {
|
|
44545
44600
|
const markerRegex = new RegExp(
|
|
44546
|
-
`${
|
|
44601
|
+
`${escapeRegExp5(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp5(CANONRY_SCHEMA_END)}`
|
|
44547
44602
|
);
|
|
44548
44603
|
const match = markerRegex.exec(rawContent);
|
|
44549
44604
|
if (match?.[1]) {
|
package/dist/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
showFirstRunNotice,
|
|
31
31
|
trackCliCommandFinished,
|
|
32
32
|
trackEvent
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-MHTZ4THA.js";
|
|
34
34
|
import {
|
|
35
35
|
CliError,
|
|
36
36
|
EXIT_SYSTEM_ERROR,
|
|
@@ -43,12 +43,14 @@ import {
|
|
|
43
43
|
isMachineFormat,
|
|
44
44
|
loadConfig,
|
|
45
45
|
loadConfigRaw,
|
|
46
|
+
measurementDraftOperationSchema,
|
|
46
47
|
printCliError,
|
|
48
|
+
runMeasurementDraftAction,
|
|
47
49
|
saveConfig,
|
|
48
50
|
saveConfigPatch,
|
|
49
51
|
systemError,
|
|
50
52
|
usageError
|
|
51
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-3HYF3LUP.js";
|
|
52
54
|
import {
|
|
53
55
|
apiKeys,
|
|
54
56
|
createClient,
|
|
@@ -56,7 +58,7 @@ import {
|
|
|
56
58
|
projects,
|
|
57
59
|
queries,
|
|
58
60
|
renderReportHtml
|
|
59
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-VUTKHFTM.js";
|
|
60
62
|
import {
|
|
61
63
|
AdsDeliverySnapshotStatuses,
|
|
62
64
|
AdsHistoricalCampaignRollupStatuses,
|
|
@@ -100,15 +102,27 @@ import {
|
|
|
100
102
|
formatMicros,
|
|
101
103
|
formatRatio,
|
|
102
104
|
formatRunErrorOneLine,
|
|
105
|
+
measurementChangesQuerySchema,
|
|
106
|
+
measurementDataQualityQuerySchema,
|
|
103
107
|
measurementDiscoveryRequestSchema,
|
|
104
108
|
measurementDiscoveryRuleSchema,
|
|
109
|
+
measurementDraftCollectionQuerySchema,
|
|
110
|
+
measurementOverviewQuerySchema,
|
|
111
|
+
measurementPlanDeactivateRequestSchema,
|
|
105
112
|
measurementPlanInputSchema,
|
|
113
|
+
measurementPortfolioSummaryQuerySchema,
|
|
114
|
+
measurementPropertyCompetitorsQuerySchema,
|
|
115
|
+
measurementPropertyQuestionsQuerySchema,
|
|
116
|
+
measurementQuerySetUpsertRequestSchema,
|
|
117
|
+
measurementQueryTemplateApplyRequestSchema,
|
|
118
|
+
measurementQueryTemplateUpsertRequestSchema,
|
|
119
|
+
measurementQuestionResultQuerySchema,
|
|
106
120
|
normalizeProjectAliases,
|
|
107
121
|
notificationEventSchema,
|
|
108
122
|
providerQuotaPolicySchema,
|
|
109
123
|
resolveProviderInput,
|
|
110
124
|
winnabilityClassSchema
|
|
111
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-43XIYBG4.js";
|
|
112
126
|
|
|
113
127
|
// src/cli.ts
|
|
114
128
|
import { pathToFileURL } from "url";
|
|
@@ -7423,16 +7437,21 @@ async function listApiKeys(format) {
|
|
|
7423
7437
|
return;
|
|
7424
7438
|
}
|
|
7425
7439
|
console.log(
|
|
7426
|
-
`${"NAME".padEnd(20)} ${"PREFIX".padEnd(11)} ${"SCOPES".padEnd(20)} ${"CREATED".padEnd(12)} ${"LAST USED".padEnd(12)} STATUS`
|
|
7440
|
+
`${"NAME".padEnd(20)} ${"PREFIX".padEnd(11)} ${"SCOPES".padEnd(20)} ${"REACH".padEnd(18)} ${"CREATED".padEnd(12)} ${"LAST USED".padEnd(12)} STATUS`
|
|
7427
7441
|
);
|
|
7428
7442
|
for (const key of keys) {
|
|
7429
7443
|
const scopes = key.scopes.join(",");
|
|
7430
7444
|
const lastUsed = key.lastUsedAt ? formatIsoDate(key.lastUsedAt) : "\u2014";
|
|
7431
7445
|
console.log(
|
|
7432
|
-
`${key.name.padEnd(20)} ${key.keyPrefix.padEnd(11)} ${scopes.padEnd(20)} ${formatIsoDate(key.createdAt).padEnd(12)} ${lastUsed.padEnd(12)} ${keyStatus(key)}`
|
|
7446
|
+
`${key.name.padEnd(20)} ${key.keyPrefix.padEnd(11)} ${scopes.padEnd(20)} ${keyReach(key).padEnd(18)} ${formatIsoDate(key.createdAt).padEnd(12)} ${lastUsed.padEnd(12)} ${keyStatus(key)}`
|
|
7433
7447
|
);
|
|
7434
7448
|
}
|
|
7435
7449
|
}
|
|
7450
|
+
function keyReach(key) {
|
|
7451
|
+
if (!key.projectId) return "full instance";
|
|
7452
|
+
if (typeof key.projectName === "string") return key.projectName;
|
|
7453
|
+
return key.projectName === null ? "(deleted project)" : key.projectId.slice(0, 8);
|
|
7454
|
+
}
|
|
7436
7455
|
async function createApiKey(opts) {
|
|
7437
7456
|
const explicitScopes = opts.scopes && opts.scopes.length > 0 ? opts.scopes : void 0;
|
|
7438
7457
|
if (opts.readOnly && explicitScopes) {
|
|
@@ -7461,7 +7480,7 @@ async function createApiKey(opts) {
|
|
|
7461
7480
|
console.log(` Prefix: ${created.keyPrefix}`);
|
|
7462
7481
|
console.log(` Scopes: ${created.scopes.join(", ")}`);
|
|
7463
7482
|
console.log(` Read-only: ${created.readOnly ? "yes" : "no"}`);
|
|
7464
|
-
console.log(`
|
|
7483
|
+
console.log(` Reach: ${keyReach(created)}`);
|
|
7465
7484
|
console.log("\nSave this now \u2014 it will not be shown again.");
|
|
7466
7485
|
}
|
|
7467
7486
|
async function showApiKeySelf(format) {
|
|
@@ -7474,6 +7493,7 @@ async function showApiKeySelf(format) {
|
|
|
7474
7493
|
console.log(`API key "${key.name}" (${key.keyPrefix})`);
|
|
7475
7494
|
console.log(` Scopes: ${key.scopes.join(", ")}`);
|
|
7476
7495
|
console.log(` Read-only: ${key.readOnly ? "yes" : "no"}`);
|
|
7496
|
+
console.log(` Reach: ${keyReach(key)}`);
|
|
7477
7497
|
console.log(` Status: ${keyStatus(key)}`);
|
|
7478
7498
|
}
|
|
7479
7499
|
async function revokeApiKey(id, format) {
|
|
@@ -15719,6 +15739,7 @@ Usage: ${usage}`, {
|
|
|
15719
15739
|
import crypto3 from "crypto";
|
|
15720
15740
|
import fs16 from "fs";
|
|
15721
15741
|
import { parse } from "yaml";
|
|
15742
|
+
import { z as z2 } from "zod";
|
|
15722
15743
|
function readPlan(source) {
|
|
15723
15744
|
const content = source === "-" ? fs16.readFileSync(0, "utf8") : fs16.readFileSync(source, "utf8");
|
|
15724
15745
|
const parsed = source.endsWith(".json") ? JSON.parse(content) : parse(content);
|
|
@@ -15729,6 +15750,171 @@ function readDiscoveryRule(source) {
|
|
|
15729
15750
|
const parsed = source.endsWith(".json") ? JSON.parse(content) : parse(content);
|
|
15730
15751
|
return measurementDiscoveryRuleSchema.parse(parsed);
|
|
15731
15752
|
}
|
|
15753
|
+
var ADVANCED_MEASUREMENT_OPERATIONS = [
|
|
15754
|
+
"setup",
|
|
15755
|
+
"overview",
|
|
15756
|
+
"portfolio-summary",
|
|
15757
|
+
"property-questions",
|
|
15758
|
+
"question-result",
|
|
15759
|
+
"property-competitors",
|
|
15760
|
+
"changes",
|
|
15761
|
+
"data-quality",
|
|
15762
|
+
"draft",
|
|
15763
|
+
"draft-targets",
|
|
15764
|
+
"draft-assignments",
|
|
15765
|
+
"draft-groups",
|
|
15766
|
+
"draft-action",
|
|
15767
|
+
"deactivate",
|
|
15768
|
+
"query-sets",
|
|
15769
|
+
"query-set-get",
|
|
15770
|
+
"query-set-upsert",
|
|
15771
|
+
"query-set-delete",
|
|
15772
|
+
"query-templates",
|
|
15773
|
+
"query-template-upsert",
|
|
15774
|
+
"query-template-delete",
|
|
15775
|
+
"query-template-apply"
|
|
15776
|
+
];
|
|
15777
|
+
var advancedIdempotencyKeySchema = z2.string().trim().min(1);
|
|
15778
|
+
var advancedResourceIdSchema = z2.string().trim().min(1);
|
|
15779
|
+
var advancedEmptyInputSchema = z2.object({}).strict();
|
|
15780
|
+
var advancedQuerySetInputSchema = z2.object({ setId: advancedResourceIdSchema }).strict();
|
|
15781
|
+
var advancedQuerySetUpsertInputSchema = advancedQuerySetInputSchema.extend({
|
|
15782
|
+
request: measurementQuerySetUpsertRequestSchema
|
|
15783
|
+
}).strict();
|
|
15784
|
+
var advancedQueryTemplateInputSchema = z2.object({ templateId: advancedResourceIdSchema }).strict();
|
|
15785
|
+
var advancedQueryTemplateUpsertInputSchema = advancedQueryTemplateInputSchema.extend({
|
|
15786
|
+
request: measurementQueryTemplateUpsertRequestSchema
|
|
15787
|
+
}).strict();
|
|
15788
|
+
var advancedQueryTemplateApplyInputSchema = advancedQueryTemplateInputSchema.extend({
|
|
15789
|
+
request: measurementQueryTemplateApplyRequestSchema,
|
|
15790
|
+
idempotencyKey: advancedIdempotencyKeySchema
|
|
15791
|
+
}).strict();
|
|
15792
|
+
var advancedDeactivateInputSchema = z2.object({
|
|
15793
|
+
request: measurementPlanDeactivateRequestSchema,
|
|
15794
|
+
idempotencyKey: advancedIdempotencyKeySchema
|
|
15795
|
+
}).strict();
|
|
15796
|
+
function readAdvancedMeasurementInput(source) {
|
|
15797
|
+
if (source === void 0) return {};
|
|
15798
|
+
const content = source === "-" ? fs16.readFileSync(0, "utf8") : fs16.readFileSync(source, "utf8");
|
|
15799
|
+
return JSON.parse(content);
|
|
15800
|
+
}
|
|
15801
|
+
var advancedMeasurementCollectionKeys = {
|
|
15802
|
+
"draft-targets": "items",
|
|
15803
|
+
"draft-assignments": "items",
|
|
15804
|
+
"draft-groups": "items",
|
|
15805
|
+
"query-sets": "querySets",
|
|
15806
|
+
"query-templates": "templates"
|
|
15807
|
+
};
|
|
15808
|
+
function isAdvancedMeasurementCollectionOperation(operation) {
|
|
15809
|
+
return operation === "draft-targets" || operation === "draft-assignments" || operation === "draft-groups" || operation === "query-sets" || operation === "query-templates";
|
|
15810
|
+
}
|
|
15811
|
+
function emitAdvancedMeasurementJsonl(operation, result) {
|
|
15812
|
+
if (!isAdvancedMeasurementCollectionOperation(operation) || typeof result !== "object" || result === null || Array.isArray(result)) return false;
|
|
15813
|
+
const collectionKey = advancedMeasurementCollectionKeys[operation];
|
|
15814
|
+
const envelope = result;
|
|
15815
|
+
const candidateRows = envelope[collectionKey];
|
|
15816
|
+
if (!Array.isArray(candidateRows)) return false;
|
|
15817
|
+
const context = {};
|
|
15818
|
+
for (const [key, value] of Object.entries(envelope)) {
|
|
15819
|
+
if (key !== collectionKey) context[key] = value;
|
|
15820
|
+
}
|
|
15821
|
+
emitJsonl([{ kind: "measurement-advanced-header", operation, ...context }]);
|
|
15822
|
+
emitJsonl(candidateRows.map((row) => row));
|
|
15823
|
+
return true;
|
|
15824
|
+
}
|
|
15825
|
+
async function runAdvancedMeasurementOperation(project, operation, source, format) {
|
|
15826
|
+
const input = readAdvancedMeasurementInput(source);
|
|
15827
|
+
const client = createApiClient();
|
|
15828
|
+
let result;
|
|
15829
|
+
switch (operation) {
|
|
15830
|
+
case "setup":
|
|
15831
|
+
advancedEmptyInputSchema.parse(input);
|
|
15832
|
+
result = await client.getMeasurementSetup(project);
|
|
15833
|
+
break;
|
|
15834
|
+
case "overview":
|
|
15835
|
+
result = await client.getMeasurementOverview(project, measurementOverviewQuerySchema.parse(input));
|
|
15836
|
+
break;
|
|
15837
|
+
case "portfolio-summary":
|
|
15838
|
+
result = await client.getMeasurementPortfolioSummary(project, measurementPortfolioSummaryQuerySchema.parse(input));
|
|
15839
|
+
break;
|
|
15840
|
+
case "property-questions":
|
|
15841
|
+
result = await client.getMeasurementPropertyQuestions(project, measurementPropertyQuestionsQuerySchema.parse(input));
|
|
15842
|
+
break;
|
|
15843
|
+
case "question-result":
|
|
15844
|
+
result = await client.getMeasurementQuestionResult(project, measurementQuestionResultQuerySchema.parse(input));
|
|
15845
|
+
break;
|
|
15846
|
+
case "property-competitors":
|
|
15847
|
+
result = await client.getMeasurementPropertyCompetitors(project, measurementPropertyCompetitorsQuerySchema.parse(input));
|
|
15848
|
+
break;
|
|
15849
|
+
case "changes":
|
|
15850
|
+
result = await client.getMeasurementChanges(project, measurementChangesQuerySchema.parse(input));
|
|
15851
|
+
break;
|
|
15852
|
+
case "data-quality":
|
|
15853
|
+
result = await client.getMeasurementDataQuality(project, measurementDataQualityQuerySchema.parse(input));
|
|
15854
|
+
break;
|
|
15855
|
+
case "draft":
|
|
15856
|
+
advancedEmptyInputSchema.parse(input);
|
|
15857
|
+
result = await client.getMeasurementPlanDraft(project);
|
|
15858
|
+
break;
|
|
15859
|
+
case "draft-targets":
|
|
15860
|
+
result = await client.getMeasurementDraftTargets(project, measurementDraftCollectionQuerySchema.parse(input));
|
|
15861
|
+
break;
|
|
15862
|
+
case "draft-assignments":
|
|
15863
|
+
result = await client.getMeasurementDraftAssignments(project, measurementDraftCollectionQuerySchema.parse(input));
|
|
15864
|
+
break;
|
|
15865
|
+
case "draft-groups":
|
|
15866
|
+
result = await client.getMeasurementDraftGroups(project, measurementDraftCollectionQuerySchema.parse(input));
|
|
15867
|
+
break;
|
|
15868
|
+
case "draft-action":
|
|
15869
|
+
result = await runMeasurementDraftAction(client, project, measurementDraftOperationSchema.parse(input));
|
|
15870
|
+
break;
|
|
15871
|
+
case "deactivate": {
|
|
15872
|
+
const parsed = advancedDeactivateInputSchema.parse(input);
|
|
15873
|
+
result = await client.deactivateMeasurementPlan(project, parsed.request, parsed.idempotencyKey);
|
|
15874
|
+
break;
|
|
15875
|
+
}
|
|
15876
|
+
case "query-sets":
|
|
15877
|
+
advancedEmptyInputSchema.parse(input);
|
|
15878
|
+
result = await client.listMeasurementQuerySets(project);
|
|
15879
|
+
break;
|
|
15880
|
+
case "query-set-get": {
|
|
15881
|
+
const parsed = advancedQuerySetInputSchema.parse(input);
|
|
15882
|
+
result = await client.getMeasurementQuerySet(project, parsed.setId);
|
|
15883
|
+
break;
|
|
15884
|
+
}
|
|
15885
|
+
case "query-set-upsert": {
|
|
15886
|
+
const parsed = advancedQuerySetUpsertInputSchema.parse(input);
|
|
15887
|
+
result = await client.upsertMeasurementQuerySet(project, parsed.setId, parsed.request);
|
|
15888
|
+
break;
|
|
15889
|
+
}
|
|
15890
|
+
case "query-set-delete": {
|
|
15891
|
+
const parsed = advancedQuerySetInputSchema.parse(input);
|
|
15892
|
+
result = await client.deleteMeasurementQuerySet(project, parsed.setId);
|
|
15893
|
+
break;
|
|
15894
|
+
}
|
|
15895
|
+
case "query-templates":
|
|
15896
|
+
advancedEmptyInputSchema.parse(input);
|
|
15897
|
+
result = await client.listMeasurementQueryTemplates(project);
|
|
15898
|
+
break;
|
|
15899
|
+
case "query-template-upsert": {
|
|
15900
|
+
const parsed = advancedQueryTemplateUpsertInputSchema.parse(input);
|
|
15901
|
+
result = await client.upsertMeasurementQueryTemplate(project, parsed.templateId, parsed.request);
|
|
15902
|
+
break;
|
|
15903
|
+
}
|
|
15904
|
+
case "query-template-delete": {
|
|
15905
|
+
const parsed = advancedQueryTemplateInputSchema.parse(input);
|
|
15906
|
+
result = await client.deleteMeasurementQueryTemplate(project, parsed.templateId);
|
|
15907
|
+
break;
|
|
15908
|
+
}
|
|
15909
|
+
case "query-template-apply": {
|
|
15910
|
+
const parsed = advancedQueryTemplateApplyInputSchema.parse(input);
|
|
15911
|
+
result = await client.applyMeasurementQueryTemplate(project, parsed.templateId, parsed.request, parsed.idempotencyKey);
|
|
15912
|
+
break;
|
|
15913
|
+
}
|
|
15914
|
+
}
|
|
15915
|
+
if (format === "jsonl" && emitAdvancedMeasurementJsonl(operation, result)) return;
|
|
15916
|
+
console.log(JSON.stringify(result ?? null, null, 2));
|
|
15917
|
+
}
|
|
15732
15918
|
async function showMeasurementPlan(project, revision) {
|
|
15733
15919
|
const client = createApiClient();
|
|
15734
15920
|
console.log(JSON.stringify(revision === void 0 ? await client.getMeasurementPlan(project) : await client.getMeasurementPlanVersion(project, revision), null, 2));
|
|
@@ -16008,6 +16194,12 @@ Usage: ${usage}`);
|
|
|
16008
16194
|
}
|
|
16009
16195
|
return { project, options: { groupKeys, targetKeys, queryIds, allProperties } };
|
|
16010
16196
|
}
|
|
16197
|
+
function advancedMeasurementOperation(value, usage) {
|
|
16198
|
+
const operation = ADVANCED_MEASUREMENT_OPERATIONS.find((candidate) => candidate === value);
|
|
16199
|
+
if (operation) return operation;
|
|
16200
|
+
throw usageError(`Error: unsupported Advanced Measurement operation: ${value ?? "(none)"}
|
|
16201
|
+
Usage: ${usage}`);
|
|
16202
|
+
}
|
|
16011
16203
|
var MEASUREMENT_PLAN_CLI_COMMANDS = [
|
|
16012
16204
|
{ path: ["measurement-plan", "show"], usage: "canonry measurement-plan show <project> [--revision N] [--format json]", options: { revision: stringOption() }, run: async (input) => {
|
|
16013
16205
|
const value = getString(input.values, "revision");
|
|
@@ -16028,6 +16220,16 @@ var MEASUREMENT_PLAN_CLI_COMMANDS = [
|
|
|
16028
16220
|
if (!stableKey) throw usageError("stable segment key is required");
|
|
16029
16221
|
return retireMeasurementPlanSegment(project, stableKey);
|
|
16030
16222
|
} },
|
|
16223
|
+
{
|
|
16224
|
+
path: ["measurement-plan", "advanced"],
|
|
16225
|
+
usage: "canonry measurement-plan advanced <project> <operation> [<json|->] [--format json|jsonl]",
|
|
16226
|
+
run: (input) => {
|
|
16227
|
+
const usage = "canonry measurement-plan advanced <project> <operation> [<json|->] [--format json|jsonl]";
|
|
16228
|
+
const project = requireProject(input, "measurement-plan.advanced", usage);
|
|
16229
|
+
const operation = advancedMeasurementOperation(input.positionals[1], usage);
|
|
16230
|
+
return runAdvancedMeasurementOperation(project, operation, input.positionals[2], input.format);
|
|
16231
|
+
}
|
|
16232
|
+
},
|
|
16031
16233
|
{
|
|
16032
16234
|
path: ["measurement-plan", "assignments", "preview"],
|
|
16033
16235
|
usage: "canonry measurement-plan assignments preview <project> [--group KEY ...] [--target-key KEY ... | --all-properties] --query-id ID [--query-id ID ...] [--format json]",
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MHTZ4THA.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-3HYF3LUP.js";
|
|
7
|
+
import "./chunk-VUTKHFTM.js";
|
|
8
|
+
import "./chunk-43XIYBG4.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|