@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 +121 -0
- package/dist/cli.mjs +98 -15
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
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
|
|
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
|
|
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(
|
|
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)
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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";
|