@getvitops/cli 5.0.0 → 7.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,126 @@
1
1
  # @getvitops/cli
2
2
 
3
+ ## 7.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c0c092b: `vitops search` now works from a `gcloud` login, and attributes API usage per site.
8
+
9
+ **Being logged in is enough.** With no `VITOPS_GOOGLE_*` set, an Application Default Credentials
10
+ login is used — the credential `gcloud auth application-default login` already wrote. It needs the
11
+ Search Console scopes, which are not in the default ADC set:
12
+
13
+ ```
14
+ gcloud auth application-default login \
15
+ --scopes=openid,https://www.googleapis.com/auth/siteverification,\
16
+ https://www.googleapis.com/auth/webmasters,https://www.googleapis.com/auth/cloud-platform
17
+ ```
18
+
19
+ Explicit `VITOPS_GOOGLE_*` vars still win. Ambient credentials are only a fallback, because a stale
20
+ local login silently overriding a deliberate CI secret is the worst available precedence.
21
+
22
+ Beyond convenience, this removes a documented footgun: an OAuth client you create yourself sits in
23
+ _Testing_ publishing status, where Google **expires the refresh token after 7 days**. The Cloud SDK
24
+ client is published, so an ADC credential doesn't rot between onboarding runs.
25
+
26
+ **New: `site.google.project`** — the Google Cloud project API usage is attributed to, sent as
27
+ `x-goog-user-project`. One project per site is what keeps each site's usage and billing separate,
28
+ and it is the same field Maps or anything else will read later, rather than a `quotaProject` here
29
+ and a `mapsProject` there.
30
+
31
+ It is **required for a user credential and wrong for a service account**, which is why it is not
32
+ sent unconditionally. A user credential authenticates through a shared OAuth client that owns no
33
+ project, so Google refuses the call outright — measured: `403 "requires a quota project, which is
34
+ not set by default"`. A service account already belongs to a project, and pointing it at a
35
+ different one would newly require `serviceusage.services.use` there, turning a working CI
36
+ credential into a 403 for no benefit.
37
+
38
+ Using it needs both APIs (`searchconsole.googleapis.com`, `siteverification.googleapis.com`)
39
+ enabled on that project, and `serviceusage.services.use` for the identity — one grant per site,
40
+ which suits an agency administering many sites from one admin identity.
41
+
42
+ An ADC credential with no `site.google.project` is now **refused before any request is made**,
43
+ naming the file the credential came from and the project gcloud recorded with it:
44
+
45
+ ```
46
+ ✖ this Google credential comes from ~/.config/gcloud/application_default_credentials.json,
47
+ which authenticates through a shared OAuth client that owns no project — so it needs
48
+ "site.google.project" in site.json to say which project the API usage belongs to.
49
+ gcloud recorded "acme-web" as the quota project for that login, so that is probably the value.
50
+ ```
51
+
52
+ That is the mistake waiting for the _second_ site: the command gets copied from whatever
53
+ onboarded the first one, the field is left out, and Google's own answer names neither the config
54
+ field nor the credential.
55
+
56
+ **Fixed: an ADC file in `GOOGLE_APPLICATION_CREDENTIALS` no longer hard-exits.** That variable is
57
+ Google's own convention _and_ where gcloud writes ADC, so the same variable carries two kinds of
58
+ credential. Parsing an `authorized_user` file as a service account failed with `service account
59
+ JSON is missing client_email or private_key` — while holding a credential the command already knew
60
+ how to use. It is now discriminated on `type`, the field Google's own libraries switch on.
61
+
62
+ **Also fixed — `search setup --dry` and `--check` are not offline.** The docs implied otherwise. The
63
+ state gather runs before the planner, so both read from Cloudflare and Google and both need
64
+ credentials. They still create and change nothing; `notify --dry` remains genuinely request-free.
65
+
66
+ New in `@getvitops/utils/indexing`: `parseAdcUser`, `adcQuotaProject`, `adcCredentialsPath` (pure,
67
+ so what is decidable is asserted without a filesystem), plus `googleHeaders` and the `GoogleAuth` /
68
+ `GoogleAuthLike` types. The seven exported Google request functions now take a token **or** a
69
+ `{ token, quotaProject }` — a bare string still works everywhere, so nothing needs changing.
70
+
71
+ ### Patch Changes
72
+
73
+ - **Added — `utils`.** `itemListGraph()`, a pure schema.org `ItemList` JSON-LD builder — a sibling
74
+ of `breadcrumbGraph()`/`localBusinessGraph()`. Extracted from `@getvitops/astro`'s `<Carousel />`
75
+ so any consumer can build a valid `ItemList` (Google's "all-in-one page" carousel format:
76
+ `ListItem.item` carrying `@type`/`name`/`url`) for content that isn't one of the six Google
77
+ carousel rich-result types (`Article`/`Recipe`/`Movie`/`Course`/`Restaurant`/`Product`) — the
78
+ entity type is no longer constrained to that set.
79
+
80
+ **Changed — `astro`.** `<Carousel />`'s `CarouselItem['type']` now accepts any string, not just
81
+ the six carousel rich-result types — non-breaking, the union only widens. It's a thin wrapper
82
+ over `itemListGraph()` now; behaviour for existing callers is unchanged.
83
+
84
+ No consumer-facing change in this release outside `utils` and `astro` — `generator`, `cli` and
85
+ `vite` still ship in this release only because the six share one version.
86
+
87
+ **Fixed — `core` (`css`/`bricks`/`tailwind` build output, all formats).** The CSS and HTML
88
+ authored inside Lit's `css`/`html` tagged templates in `<wc-*>` component sources previously
89
+ survived JS minification verbatim — a JS minifier treats a template literal's cooked text as
90
+ opaque, so `elements.js` shipped ~690 lines of indented, unminified CSS (and the equivalent in
91
+ HTML) inside an otherwise-minified 886-line file. Two source-level build transforms now collapse
92
+ both before bundling: `postcss` + `postcss-lit` + `cssnano` for `css` templates, `minify-html-literals`
93
+ (`conservativeCollapse: true`, so whitespace between inline elements in light-DOM components never
94
+ collapses to zero) for `html` templates. `elements.js` drops from 886 lines / ~101 KB to 49 lines /
95
+ ~96 KB. A second build step deduplicates the 17 `@license` comment blocks (only 6 are textually
96
+ distinct) down to one verbatim copy of each — required by BSD-3/MIT/Apache-2.0's notice-retention
97
+ terms either way, just no longer repeated once per source file that carries it. No API change;
98
+ build-time only, and none of the added tooling (`postcss`, `postcss-lit`, `cssnano`,
99
+ `minify-html-literals`) is a runtime or published dependency.
100
+
101
+ - Updated dependencies [c0c092b]
102
+ - Updated dependencies
103
+ - Updated dependencies [aad43b7]
104
+ - Updated dependencies [2b50f9e]
105
+ - Updated dependencies [a6a8b39]
106
+ - Updated dependencies [7b391b3]
107
+ - Updated dependencies [eb9ac79]
108
+ - @getvitops/utils@7.0.0
109
+ - @getvitops/generator@7.0.0
110
+
111
+ ## 6.0.0
112
+
113
+ **No consumer-facing change in this package.** The major is the toolchain's shared version, bumped
114
+ in lockstep for the carousel rework in `@getvitops/core` / `generator` / `astro`. Every command
115
+ behaves as it did in 5.0.0; `vitops docs components` and `vitops docs css` will report the new
116
+ carousel vocabulary, because they render live from the generator.
117
+
118
+ ### Patch Changes
119
+
120
+ - Updated dependencies
121
+ - @getvitops/generator@6.0.0
122
+ - @getvitops/utils@6.0.0
123
+
3
124
  ## 5.0.0
4
125
 
5
126
  ### Major Changes
package/dist/cli.mjs CHANGED
@@ -3,10 +3,11 @@ import { createRequire } from "node:module";
3
3
  import { parseArgs } from "node:util";
4
4
  import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
5
5
  import { dirname, extname, join, parse, relative, resolve } from "node:path";
6
+ import { homedir } from "node:os";
6
7
  import { pathToFileURL } from "node:url";
7
8
  import { JURISDICTION_COUNTRIES, NUMERIC_STEPS, SCHEMA_LOCAL_PATH, buildIconSprite, defaultConfig, enabledDocs, expandPalette, functionalRole, generate, generateDocs, generateLegal, isConfig, movedTokens, processorsMissingLocation, resolveConfig, resolveInput, resolveTheme, roleColorUtilities, roleHue, roleKind, validate } from "@getvitops/generator";
8
9
  import { collectIconRefs, generateFavicons, generateIconInclude, resolveIcon, scanFiles } from "@getvitops/utils";
9
- import { SCOPES, SNAPSHOT_PATH, collectEntries, formatPlan, googleAccessToken, inspectUrl, keyFileContents, newKey, parseServiceAccount, plan, readSnapshot, resolveKeyLocation, resolveSitemapUrl, submitBatch, submitSitemap, toSnapshot, verifyKeyFile, writeSnapshot } from "@getvitops/utils/indexing";
10
+ import { SCOPES, SNAPSHOT_PATH, adcCredentialsPath, adcQuotaProject, collectEntries, formatPlan, googleAccessToken, inspectUrl, keyFileContents, newKey, parseAdcUser, parseServiceAccount, plan, readSnapshot, resolveKeyLocation, resolveSitemapUrl, submitBatch, submitSitemap, toSnapshot, verifyKeyFile, writeSnapshot } from "@getvitops/utils/indexing";
10
11
  import { addSite, backoffSchedule, createApexTxt, findZoneId, formatPlan as formatPlan$1, formatSummary, getSite, getVerificationToken, getWebResource, hasDrift, listApexTxt, ownerUnion, plan as plan$1, refreshTokenGrant, siteUrlFor, updateOwners, verifyWebResource } from "@getvitops/utils/onboarding";
11
12
  import { AD_PLATFORMS, AD_PROVIDER_KEYS, formatPlan as formatPlan$2, formatSummary as formatSummary$1, hasDrift as hasDrift$1, isAdProvider, missingFields, plan as plan$2, renderTag, tagId } from "@getvitops/utils/ads";
12
13
  import { PLATFORM_PARAMS } from "@getvitops/utils/tracking";
@@ -278,6 +279,16 @@ Search setup options:
278
279
  scoped to siteverification + webmasters). Granting a Google Group Full-User
279
280
  access has no API and is surfaced as a reminder.
280
281
 
282
+ Or just be logged in: with no VITOPS_GOOGLE_* set, an Application Default
283
+ Credentials login is used —
284
+ gcloud auth application-default login \\
285
+ --scopes=openid,https://www.googleapis.com/auth/siteverification,\\
286
+ https://www.googleapis.com/auth/webmasters,https://www.googleapis.com/auth/cloud-platform
287
+ ADC authenticates through a shared OAuth client that owns no project, so it also
288
+ needs site.google.project in the config (sent as x-goog-user-project), both APIs
289
+ enabled on that project, and serviceusage.services.use on it. One project per site
290
+ is what keeps each site's API usage and billing separate.
291
+
281
292
  Search notify options:
282
293
  -i, --input <path> Site config carrying seo.indexing (default: ./site.json)
283
294
  --site-env <env> Environment to notify for (default: production). An
@@ -1963,6 +1974,7 @@ function loadServiceAccount() {
1963
1974
  const path = process.env.GOOGLE_APPLICATION_CREDENTIALS;
1964
1975
  const raw = inline ?? (path && existsSync(path) ? readFileSync(path, "utf8") : void 0);
1965
1976
  if (!raw) return void 0;
1977
+ if (parseAdcUser(raw)) return void 0;
1966
1978
  try {
1967
1979
  return parseServiceAccount(raw);
1968
1980
  } catch (err) {
@@ -2038,7 +2050,8 @@ async function cmdSearchNotify(argv) {
2038
2050
  console.log(` Then serve it at /${key}.txt — \`vitops search notify --write-key public\`.`);
2039
2051
  return;
2040
2052
  }
2041
- const config = toIndexingConfig(await loadConfig(resolve(values.input), values["site-env"]), values["site-env"]);
2053
+ const site = await loadConfig(resolve(values.input), values["site-env"]);
2054
+ const config = toIndexingConfig(site, values["site-env"]);
2042
2055
  if (values["write-key"] !== void 0) {
2043
2056
  const key = config.indexNow?.key;
2044
2057
  if (!key) fail("no site.seo.indexing.indexNow.key in the config — generate one with --new-key");
@@ -2077,10 +2090,10 @@ async function cmdSearchNotify(argv) {
2077
2090
  if (!p.searchConsole.siteUrl) fail("--check needs seo.indexing.searchConsole.siteUrl (the Search Console property)");
2078
2091
  if (!credential) fail(`--check needs a Search Console credential — ${CREDENTIAL_HINT}`);
2079
2092
  if (p.check.length === 0) fail("--check needs seo.indexing.priorityUrls — the pages whose indexing matters");
2080
- const token = await googleAccessToken(credential);
2093
+ const auth = searchAuth(await googleAccessToken(credential), credential, site, values.input);
2081
2094
  console.log(`\nInspecting ${p.check.length} priority URL(s):`);
2082
2095
  for (const url of p.check) {
2083
- const r = await inspectUrl(token, p.searchConsole.siteUrl, url);
2096
+ const r = await inspectUrl(auth, p.searchConsole.siteUrl, url);
2084
2097
  if (r.error) {
2085
2098
  console.log(` ? ${url} — ${r.error}`);
2086
2099
  failed = true;
@@ -2119,7 +2132,7 @@ async function cmdSearchNotify(argv) {
2119
2132
  }
2120
2133
  if (p.searchConsole.enabled) if (!credential) console.log(`· Search Console: skipped — no credential (${CREDENTIAL_HINT})`);
2121
2134
  else {
2122
- const r = await submitSitemap(await googleAccessToken(credential), p.searchConsole.siteUrl, sitemapUrl);
2135
+ const r = await submitSitemap(searchAuth(await googleAccessToken(credential), credential, site, values.input), p.searchConsole.siteUrl, sitemapUrl);
2123
2136
  if (r.ok) console.log(`✓ Search Console: sitemap resubmitted`);
2124
2137
  else {
2125
2138
  console.error(`✖ Search Console: ${r.message}`);
@@ -2169,11 +2182,53 @@ function loadCloudflareToken() {
2169
2182
  * the sites. All-or-nothing — a partial set is a misconfiguration worth naming
2170
2183
  * rather than a reason to run degraded.
2171
2184
  */
2185
+ /**
2186
+ * Set when the Google credential came from an ADC file rather than the env vars.
2187
+ *
2188
+ * Provenance matters for exactly one check: an ADC credential authenticates through
2189
+ * a shared OAuth client that owns no project, so it CANNOT work without
2190
+ * `site.google.project`, while a credential from your own OAuth client works fine
2191
+ * without one. Knowing which we hold is what turns Google's "requires a quota
2192
+ * project" 403 into an error that names the field and the likely value.
2193
+ */
2194
+ let adcOrigin;
2195
+ /**
2196
+ * Refuse an ADC credential with no project, before spending a request on a 403.
2197
+ *
2198
+ * This is the mistake waiting for the *second* site: the command is copied from
2199
+ * whatever onboarded the first one, `site.google.project` is left out, and Google
2200
+ * answers with a message about quota projects that names neither the config field
2201
+ * nor the file the credential came from.
2202
+ */
2203
+ function requireAdcProject(project, input) {
2204
+ if (!adcOrigin || project) return;
2205
+ fail(`this Google credential comes from ${adcOrigin.path}, which authenticates through a shared OAuth client that owns no project — so it needs "site.google.project" in ${input} to say which project the API usage belongs to.` + (adcOrigin.project ? `\n gcloud recorded "${adcOrigin.project}" as the quota project for that login, so that is probably the value.` : "") + "\n One project per site keeps each site's usage and billing separate. That project also needs searchconsole.googleapis.com and siteverification.googleapis.com enabled, and the identity needs serviceusage.services.use on it.\n Or set VITOPS_GOOGLE_CLIENT_ID / _CLIENT_SECRET / _REFRESH_TOKEN to use your own OAuth client instead.");
2206
+ }
2172
2207
  function loadGoogleOAuth() {
2173
2208
  const clientId = process.env.VITOPS_GOOGLE_CLIENT_ID;
2174
2209
  const clientSecret = process.env.VITOPS_GOOGLE_CLIENT_SECRET;
2175
2210
  const refreshToken = process.env.VITOPS_GOOGLE_REFRESH_TOKEN;
2176
- if (!clientId && !clientSecret && !refreshToken) return void 0;
2211
+ if (!clientId && !clientSecret && !refreshToken) {
2212
+ const path = process.env.GOOGLE_APPLICATION_CREDENTIALS ?? adcCredentialsPath({
2213
+ env: process.env,
2214
+ platform: process.platform,
2215
+ home: homedir(),
2216
+ join
2217
+ });
2218
+ if (path && existsSync(path)) {
2219
+ const raw = readFileSync(path, "utf8");
2220
+ const adc = parseAdcUser(raw);
2221
+ if (adc) {
2222
+ const recorded = adcQuotaProject(raw);
2223
+ adcOrigin = recorded ? {
2224
+ path,
2225
+ project: recorded
2226
+ } : { path };
2227
+ return adc;
2228
+ }
2229
+ }
2230
+ return;
2231
+ }
2177
2232
  const missing = [
2178
2233
  ["VITOPS_GOOGLE_CLIENT_ID", clientId],
2179
2234
  ["VITOPS_GOOGLE_CLIENT_SECRET", clientSecret],
@@ -2213,6 +2268,24 @@ function loadSearchCredential() {
2213
2268
  oauth
2214
2269
  } : void 0;
2215
2270
  }
2271
+ /**
2272
+ * Attach the site's Google project to a token — for a **user** credential only.
2273
+ *
2274
+ * A user credential (an ADC login, or `VITOPS_GOOGLE_*`) authenticates through a
2275
+ * shared OAuth client that owns no project, so Google refuses the call outright
2276
+ * without `x-goog-user-project`. A service account already belongs to a project, and
2277
+ * pointing it at a *different* one would newly require `serviceusage.services.use`
2278
+ * there — turning a working CI credential into a 403 for no benefit. So the header
2279
+ * is sent exactly when it is needed, never as a default.
2280
+ */
2281
+ function searchAuth(token, credential, cfg, input) {
2282
+ const project = cfg.site?.google?.project;
2283
+ if (credential.kind === "oauth") requireAdcProject(project, input);
2284
+ return project && credential.kind === "oauth" ? {
2285
+ token,
2286
+ quotaProject: project
2287
+ } : token;
2288
+ }
2216
2289
  /** Both spellings, for an error that has to tell you what to actually set. */
2217
2290
  const CREDENTIAL_HINT = "a service account in VITOPS_GSC_SERVICE_ACCOUNT / GOOGLE_APPLICATION_CREDENTIALS, or a user OAuth credential in VITOPS_GOOGLE_CLIENT_ID / VITOPS_GOOGLE_CLIENT_SECRET / VITOPS_GOOGLE_REFRESH_TOKEN";
2218
2291
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -2243,6 +2316,8 @@ async function cmdSearch(argv) {
2243
2316
  * Idempotent by construction: the planner decides each step from the domain's live
2244
2317
  * state, so a re-run of a fully-onboarded domain is all skips. `--check` reports
2245
2318
  * drift and exits non-zero without mutating; `--dry` prints the plan and stops.
2319
+ * Neither is offline: the state gather runs before the planner either way, so both
2320
+ * read from Cloudflare and Google and both need credentials.
2246
2321
  * DNS is only ever *created* — the verification TXT — never edited or removed.
2247
2322
  *
2248
2323
  * Search Console has no user/permission API, so granting a Google Group Full-User
@@ -2273,11 +2348,14 @@ async function cmdSearchSetup(argv) {
2273
2348
  },
2274
2349
  allowPositionals: false
2275
2350
  });
2276
- const domains = toSearchConsoleSetup(await loadConfig(resolve(values.input), values["site-env"]), values.domain);
2351
+ const cfg = await loadConfig(resolve(values.input), values["site-env"]);
2352
+ const domains = toSearchConsoleSetup(cfg, values.domain);
2277
2353
  if (domains.length === 0) fail("no domains in site.searchConsole — add domains to onboard (keyed by bare hostname)");
2278
2354
  const cfToken = loadCloudflareToken();
2279
2355
  const oauth = loadGoogleOAuth();
2280
- if (values.dry && (!cfToken || !oauth)) {
2356
+ const project = cfg.site?.google?.project;
2357
+ const usableOauth = oauth && !(adcOrigin && !project);
2358
+ if (values.dry && (!cfToken || !usableOauth)) {
2281
2359
  const blank = {
2282
2360
  zoneId: "",
2283
2361
  txtPresent: false,
@@ -2286,28 +2364,33 @@ async function cmdSearchSetup(argv) {
2286
2364
  propertyExists: false
2287
2365
  };
2288
2366
  console.log(formatPlan$1(plan$1({ domains }, new Map(domains.map((d) => [d.domain, blank])))));
2289
- const missing = [...cfToken ? [] : ["CLOUDFLARE_API_TOKEN (Zone:DNS:Edit + Zone:Read)"], ...oauth ? [] : ["VITOPS_GOOGLE_CLIENT_ID / VITOPS_GOOGLE_CLIENT_SECRET / VITOPS_GOOGLE_REFRESH_TOKEN"]];
2367
+ const missing = [...cfToken ? [] : ["CLOUDFLARE_API_TOKEN (Zone:DNS:Edit + Zone:Read)"], ...usableOauth ? [] : oauth ? [`site.google.project in ${values.input} (this ADC login has no project)`] : ["VITOPS_GOOGLE_CLIENT_ID / VITOPS_GOOGLE_CLIENT_SECRET / VITOPS_GOOGLE_REFRESH_TOKEN"]];
2290
2368
  console.log(`\n(--dry, and no credentials set: planned from scratch rather than from live state, so nothing here is reported as already done.\n A real run needs ${missing.join(" and ")}.)`);
2291
2369
  return;
2292
2370
  }
2293
2371
  if (!cfToken) fail("set CLOUDFLARE_API_TOKEN (a Zone:DNS:Edit token — the standard \"Edit zone DNS\" template, which also carries the Zone:Read that the zone-by-name lookup needs)");
2294
- if (!oauth) fail("set VITOPS_GOOGLE_CLIENT_ID / VITOPS_GOOGLE_CLIENT_SECRET / VITOPS_GOOGLE_REFRESH_TOKEN — a user OAuth token scoped to siteverification + webmasters");
2372
+ if (!oauth) fail("set VITOPS_GOOGLE_CLIENT_ID / VITOPS_GOOGLE_CLIENT_SECRET / VITOPS_GOOGLE_REFRESH_TOKEN — a user OAuth token scoped to siteverification + webmasters — or run `gcloud auth application-default login` with the siteverification + webmasters + cloud-platform scopes (see --help)");
2373
+ requireAdcProject(project, values.input);
2295
2374
  const token = await refreshTokenGrant(oauth);
2375
+ const auth = project ? {
2376
+ token,
2377
+ quotaProject: project
2378
+ } : token;
2296
2379
  const states = /* @__PURE__ */ new Map();
2297
2380
  const verifyTokens = /* @__PURE__ */ new Map();
2298
2381
  const webIds = /* @__PURE__ */ new Map();
2299
2382
  for (const d of domains) {
2300
2383
  const zone = await findZoneId(cfToken, d.domain);
2301
2384
  if (!zone.ok || !zone.zoneId) fail(`${d.domain}: ${zone.message}`);
2302
- const vt = await getVerificationToken(token, d.domain);
2385
+ const vt = await getVerificationToken(auth, d.domain);
2303
2386
  if (!vt.ok || !vt.token) fail(`${d.domain}: could not obtain a verification token — ${vt.message}`);
2304
2387
  verifyTokens.set(d.domain, vt.token);
2305
2388
  const txt = await listApexTxt(cfToken, zone.zoneId, d.domain);
2306
2389
  if (!txt.ok) fail(`${d.domain}: ${txt.message}`);
2307
- const wr = await getWebResource(token, d.domain);
2390
+ const wr = await getWebResource(auth, d.domain);
2308
2391
  if (!wr.ok) fail(`${d.domain}: ${wr.message}`);
2309
2392
  if (wr.id) webIds.set(d.domain, wr.id);
2310
- const site = await getSite(token, siteUrlFor(d.domain));
2393
+ const site = await getSite(auth, siteUrlFor(d.domain));
2311
2394
  if (!site.ok) fail(`${d.domain}: ${site.message}`);
2312
2395
  states.set(d.domain, {
2313
2396
  zoneId: zone.zoneId,
@@ -2360,7 +2443,7 @@ async function cmdSearchSetup(argv) {
2360
2443
  if (verified) result.verified = "yes";
2361
2444
  else {
2362
2445
  for (let attempt = 0; attempt < delays.length; attempt++) {
2363
- const v = await verifyWebResource(token, d.domain);
2446
+ const v = await verifyWebResource(auth, d.domain);
2364
2447
  if (v.ok) {
2365
2448
  verified = true;
2366
2449
  if (v.id) webIds.set(d.domain, v.id);
@@ -2381,7 +2464,7 @@ async function cmdSearchSetup(argv) {
2381
2464
  }
2382
2465
  if (dp.property.action === "skip") result.property = "present";
2383
2466
  else {
2384
- const r = await addSite(token, dp.siteUrl);
2467
+ const r = await addSite(auth, dp.siteUrl);
2385
2468
  if (!r.ok) {
2386
2469
  console.error(`✖ ${d.domain} property: ${r.message}`);
2387
2470
  result.property = "failed";