@getvitops/utils 2.0.0 → 3.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 +192 -0
- package/README.md +33 -0
- package/dist/indexing/index.d.mts +277 -0
- package/dist/indexing/index.d.mts.map +1 -0
- package/dist/indexing/index.mjs +547 -0
- package/dist/indexing/index.mjs.map +1 -0
- package/dist/media/index.d.mts +277 -0
- package/dist/media/index.d.mts.map +1 -0
- package/dist/media/index.mjs +533 -0
- package/dist/media/index.mjs.map +1 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,197 @@
|
|
|
1
1
|
# @getvitops/utils
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 6e68ace: **Breaking:** the site config is now a three-section `Config` — `designSystem`,
|
|
8
|
+
`organization`, `site`.
|
|
9
|
+
|
|
10
|
+
The flat `SiteConfig` held the company (`organization`, `contact`, `locations`) and the
|
|
11
|
+
deployment (`analytics`, `environments`, `seo`, `legal`) as peers, so no single noun
|
|
12
|
+
described it, and a second site sharing the same company had no way to say so. The three
|
|
13
|
+
sections split those apart: several sites can now carry one `organization` and differ only
|
|
14
|
+
in `site`.
|
|
15
|
+
|
|
16
|
+
**Migrating.** `designSystem` stays at the root, and the fields already under
|
|
17
|
+
`organization` stay where they are. Everything else moves into one of two sections:
|
|
18
|
+
- → `site`: `defaultLocale`, `locales`, `domains`, `dns`, `cloudflare`, `environments`,
|
|
19
|
+
`abTesting`, `fonts`, `tags`, `postTypes`, `galleries`, `testimonials`, `templates`,
|
|
20
|
+
`navigation`, `seo`, `analytics`, `notifications`, `tracking`, `security`, `legal`,
|
|
21
|
+
`icons`, `favicon`, `deployment`
|
|
22
|
+
- → `organization`: `contact`, `primaryLocation`, `locations`, `services`, `links`
|
|
23
|
+
|
|
24
|
+
You do not have to work this out from the list: `vitops validate` detects a pre-3.0 flat
|
|
25
|
+
config and prints every move by name, rather than reporting a dozen unknown keys.
|
|
26
|
+
|
|
27
|
+
```jsonc
|
|
28
|
+
{
|
|
29
|
+
"designSystem": {
|
|
30
|
+
"themes": {
|
|
31
|
+
"default": {
|
|
32
|
+
/* … */
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
"organization": {
|
|
37
|
+
"name": "Acme",
|
|
38
|
+
"contact": "hq",
|
|
39
|
+
"locations": {
|
|
40
|
+
"hq": {
|
|
41
|
+
/* … */
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
"site": {
|
|
46
|
+
"defaultLocale": "en",
|
|
47
|
+
"domains": {
|
|
48
|
+
/* … */
|
|
49
|
+
},
|
|
50
|
+
"analytics": {
|
|
51
|
+
/* … */
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Renamed exports** (`@getvitops/generator`): `SiteConfigSchema` → `ConfigSchema`,
|
|
58
|
+
`SiteConfig` → `Config`, `validateSite` → `validateConfig`, `resolveSiteConfig` →
|
|
59
|
+
`resolveConfig`, `isSiteConfig` → `isConfig`, `siteJsonSchema` → `configJsonSchema`,
|
|
60
|
+
`SITE_SCHEMA_URL` → `CONFIG_SCHEMA_URL`, `SiteValidationResult` →
|
|
61
|
+
`ConfigValidationResult`. `ResolvedInput.site` is now `ResolvedInput.config`. New:
|
|
62
|
+
`OrganizationConfig` and `SiteSection` for the two sections.
|
|
63
|
+
|
|
64
|
+
**Renamed schema file:** `@getvitops/generator/site.schema.json` →
|
|
65
|
+
`@getvitops/generator/config.schema.json`. Update the `$schema` key in your config.
|
|
66
|
+
|
|
67
|
+
Option names are unchanged — `vitops({ site: { input } })`, the Vite plugin's `site`,
|
|
68
|
+
and `generate({ site })` still point at the config file.
|
|
69
|
+
|
|
70
|
+
**The Astro integration is now `vitops()`, not `getvitops()`.** It is a default export,
|
|
71
|
+
so the name is yours and nothing breaks on upgrade, but every example now reads
|
|
72
|
+
`import vitops from '@getvitops/astro'` — matching the Vite plugin, which has always been
|
|
73
|
+
`vitops`. The `@getvitops/*` package scope and the internal `virtual:getvitops/*` module
|
|
74
|
+
ids are unchanged; neither is an import name.
|
|
75
|
+
|
|
76
|
+
**Added:** a generated config authoring reference — `vitops docs config`, `docs/config.md`
|
|
77
|
+
in the OKF bundle, and _Config reference_ on the docs site. It is walked from the published
|
|
78
|
+
JSON Schema by the same helper that renders the `design-system.json` reference, so it
|
|
79
|
+
cannot describe a field validation does not accept.
|
|
80
|
+
|
|
81
|
+
## 2.1.0
|
|
82
|
+
|
|
83
|
+
### Minor Changes
|
|
84
|
+
|
|
85
|
+
- 20e518e: Add `vitops indexing` — tell search engines about a deploy, instead of opening Search Console by hand.
|
|
86
|
+
|
|
87
|
+
Configure it in your site config under `seo.indexing`, then run it after deploying:
|
|
88
|
+
|
|
89
|
+
```jsonc
|
|
90
|
+
"seo": {
|
|
91
|
+
"indexing": {
|
|
92
|
+
"indexNow": { "key": "…" },
|
|
93
|
+
"searchConsole": { "siteUrl": "sc-domain:acme.ca" },
|
|
94
|
+
"priorityUrls": ["https://acme.ca/", "https://acme.ca/services"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
vitops indexing --dry # print the plan, make no requests
|
|
101
|
+
vitops indexing # submit
|
|
102
|
+
vitops indexing --check # a day or two later: did Google actually index them?
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
It reads your sitemap, diffs it against the previous run, and submits only what changed —
|
|
106
|
+
pinging **IndexNow** and re-submitting your sitemap through the **Search Console API**.
|
|
107
|
+
`--check` inspects `priorityUrls` and exits non-zero on a page Google hasn't indexed, so a
|
|
108
|
+
scheduled CI job can catch a page that quietly fell out of the index.
|
|
109
|
+
|
|
110
|
+
**Be clear-eyed about what this can do.** Google exposes no API that requests indexing — the
|
|
111
|
+
button in Search Console isn't available anywhere, URL Inspection is read-only, and the sitemap
|
|
112
|
+
ping endpoint was removed in 2023. IndexNow reaches Bing, Yandex, Naver, Seznam and Yep; Google
|
|
113
|
+
doesn't participate. So this automates every sanctioned step and then verifies the outcome; it
|
|
114
|
+
does not make Google re-index on demand, and nothing can. Google's Indexing API is deliberately
|
|
115
|
+
not wired: it's scoped to job postings and livestreams, and using it for ordinary pages violates
|
|
116
|
+
its terms.
|
|
117
|
+
|
|
118
|
+
Also new, and worth wiring at the same time — **`gitLastmod()` for real sitemap dates**:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
import vitops, { gitLastmod } from '@getvitops/astro';
|
|
122
|
+
vitops({ sitemap: { serialize: await gitLastmod() } });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`@astrojs/sitemap` emits no `<lastmod>` by default, which means a crawler is told your pages
|
|
126
|
+
exist but never that one changed — and it's what lets `vitops indexing` submit a handful of URLs instead
|
|
127
|
+
of all of them. `gitLastmod()` derives each date from the source file's last commit, and leaves a
|
|
128
|
+
page alone rather than guessing (dynamic routes, ambiguous slugs, shallow clones): an inaccurate
|
|
129
|
+
`lastmod` is worse than none, because Google stops trusting the field site-wide.
|
|
130
|
+
|
|
131
|
+
Requires `fetch-depth: 0` in CI — the default shallow clone has no history to read, and it warns
|
|
132
|
+
when that's the case.
|
|
133
|
+
|
|
134
|
+
**Credentials.** IndexNow's key is public by design (it's served at `/<key>.txt` as the ownership
|
|
135
|
+
proof), so it lives in your config, and the Astro integration writes the file into `public/` for
|
|
136
|
+
you — `vitops indexing --new-key` generates one, `--write-key <dir>` writes it for non-Astro stacks.
|
|
137
|
+
Search Console needs a service account in `VITOPS_GSC_SERVICE_ACCOUNT` or
|
|
138
|
+
`GOOGLE_APPLICATION_CREDENTIALS`, added as an owner of the property; it's never read from the
|
|
139
|
+
config file.
|
|
140
|
+
|
|
141
|
+
Persist `.vitops/` between runs (a CI cache) — that's the changed-URL state, and without it every
|
|
142
|
+
run submits everything.
|
|
143
|
+
|
|
144
|
+
An environment whose `robots` policy says `noindex` is refused outright, so pointing this at
|
|
145
|
+
staging can't publish it to a search engine.
|
|
146
|
+
|
|
147
|
+
- 9bf975a: Add `vitops media` — encode raw video into web-ready outputs, instead of hand-rolling an ffmpeg
|
|
148
|
+
script per project.
|
|
149
|
+
|
|
150
|
+
Keep unprocessed video in a `raw/` directory and run:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
vitops media --raw raw --out src/assets/processed
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Each source becomes three files: **VP9/WebM**, an **H.264/MP4** fallback, and a **JPG poster**.
|
|
157
|
+
Import them like any other asset, so your bundler content-hashes them:
|
|
158
|
+
|
|
159
|
+
```astro
|
|
160
|
+
---
|
|
161
|
+
import hero from '../assets/processed/hero.webm';
|
|
162
|
+
import poster from '../assets/processed/hero.jpg';
|
|
163
|
+
---
|
|
164
|
+
<video poster={poster.src} autoplay muted loop playsinline>
|
|
165
|
+
<source src={hero} type="video/webm" />
|
|
166
|
+
</video>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
In Astro it's an integration option — `vitops({ css, media: { raw: 'raw', out: 'src/assets/processed' } })`
|
|
170
|
+
— and it runs in the same pass that generates your CSS. `@getvitops/vite` gains a matching `media`
|
|
171
|
+
option, and `@getvitops/utils/media` exports `processMedia()` for anything else.
|
|
172
|
+
|
|
173
|
+
Defaults: capped at 1920px wide, CRF 32, audio dropped (the common case is a muted autoplay loop),
|
|
174
|
+
poster from frame 0. All of them are flags. `--dry` prints exactly what a run would do.
|
|
175
|
+
|
|
176
|
+
**Runs are cached**, on source content plus encode settings, in `.vitops/media-manifest.json` — a
|
|
177
|
+
24 MB clip that took 88 seconds the first time takes 0.14 seconds the second. A missing output
|
|
178
|
+
re-encodes; a corrupt manifest re-encodes everything. Neither ever reads as "already done", because
|
|
179
|
+
that failure is silent and no rebuild would fix it.
|
|
180
|
+
|
|
181
|
+
**Commit the outputs and the manifest.** A fresh CI clone has neither and would re-encode from
|
|
182
|
+
scratch; committing both means CI never needs ffmpeg. It also keeps your history clean — ffmpeg
|
|
183
|
+
output isn't byte-reproducible across versions, so a CI re-encode would rewrite every video on any
|
|
184
|
+
toolchain bump. Use `--force` when you mean to re-encode.
|
|
185
|
+
|
|
186
|
+
**`ffmpeg` is an external tool, not an npm dependency** — install it yourself (`brew install
|
|
187
|
+
ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails without it rather
|
|
188
|
+
than skipping: a page referencing a video that was never encoded is broken, not degraded.
|
|
189
|
+
|
|
190
|
+
Two defaults worth knowing about. The MP4 exists because older iOS and the in-app webviews inside
|
|
191
|
+
social apps still don't decode VP9, and it's written with `+faststart` — without which a browser
|
|
192
|
+
downloads the whole file before showing frame one. And a poster taken from frame 0 is often black
|
|
193
|
+
on a clip that fades in; that's `--poster-time`, not a bug.
|
|
194
|
+
|
|
3
195
|
## 2.0.0
|
|
4
196
|
|
|
5
197
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -25,6 +25,39 @@ await generateFavicons({
|
|
|
25
25
|
Also available via the CLI (`vitops favicon -i logo.svg -o public`) and the Vite plugin's
|
|
26
26
|
`favicon` option.
|
|
27
27
|
|
|
28
|
+
## Video encoding (`@getvitops/utils/media`)
|
|
29
|
+
|
|
30
|
+
Encode a directory of raw video into VP9/WebM + an H.264/MP4 fallback + a JPG poster frame.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { processMedia } from '@getvitops/utils/media';
|
|
34
|
+
|
|
35
|
+
const { written, skipped } = await processMedia({
|
|
36
|
+
raw: 'raw',
|
|
37
|
+
out: 'src/assets/processed',
|
|
38
|
+
config: { maxWidth: 1920, crf: 32 },
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Its own subpath, like `./indexing`: it shells out to **`ffmpeg`**, which must be on `PATH`, and
|
|
43
|
+
it throws rather than skipping when it isn't.
|
|
44
|
+
|
|
45
|
+
Runs are cached in `.vitops/media-manifest.json` on source content and encode settings, so a
|
|
46
|
+
second run costs a hash per file. `planMedia` decides all of it and touches no filesystem, which
|
|
47
|
+
is what makes `dry: true` a complete account of a run rather than an approximation — the same
|
|
48
|
+
split `./indexing` makes.
|
|
49
|
+
|
|
50
|
+
Also available via the CLI (`vitops media`), the Vite plugin's `media` option, and
|
|
51
|
+
`getvitops({ media })` in Astro. See the [CLI readme](https://www.npmjs.com/package/@getvitops/cli)
|
|
52
|
+
for why the outputs are meant to be committed.
|
|
53
|
+
|
|
54
|
+
## Search-engine notification (`@getvitops/utils/indexing`)
|
|
55
|
+
|
|
56
|
+
The engine behind `vitops indexing`: sitemap parsing, the changed-URL diff against the previous
|
|
57
|
+
run's snapshot, IndexNow submission, and the Search Console API calls (sitemap resubmit + read-only
|
|
58
|
+
URL inspection). `plan()` is pure — no network, no filesystem, no clock — and `indexnow.ts` /
|
|
59
|
+
`gsc.ts` execute a plan without deciding anything.
|
|
60
|
+
|
|
28
61
|
## Changelog
|
|
29
62
|
|
|
30
63
|
This package's history: [`CHANGELOG.md`](./CHANGELOG.md) (shipped in the npm tarball, so it
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
//#region src/indexing/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The indexing module's own option surface.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately structural rather than `Config['seo']['indexing']` imported
|
|
6
|
+
* from `@getvitops/generator`: the generator already depends on this package, so
|
|
7
|
+
* importing back would be a cycle. The vocabulary mirrors that block field for
|
|
8
|
+
* field, so the CLI's adapter is a flat map — the same arrangement, and for the
|
|
9
|
+
* same reason, as `GetvitopsSeoOptions` in `@getvitops/astro`.
|
|
10
|
+
*/
|
|
11
|
+
/** IndexNow submission settings. The key is public by design — see `key`. */
|
|
12
|
+
interface IndexNowConfig {
|
|
13
|
+
/**
|
|
14
|
+
* The IndexNow key. **Not a secret**: the engine fetches it back from
|
|
15
|
+
* `keyLocation` to prove you control the host, so it is published either way.
|
|
16
|
+
*/
|
|
17
|
+
key: string;
|
|
18
|
+
/** Absolute URL of the key file. Defaults to `<canonical>/<key>.txt`. */
|
|
19
|
+
keyLocation?: string | undefined;
|
|
20
|
+
/** Endpoint (default `https://api.indexnow.org/indexnow`). */
|
|
21
|
+
endpoint?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Google Search Console property settings. The credential never lives here. */
|
|
24
|
+
interface SearchConsoleConfig {
|
|
25
|
+
/** `sc-domain:acme.ca`, or the URL-prefix form `https://acme.ca/`. */
|
|
26
|
+
siteUrl: string;
|
|
27
|
+
/** Re-submit the sitemap on each run (default true). */
|
|
28
|
+
resubmitSitemap?: boolean | undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Everything `plan()` needs to decide what to do. */
|
|
31
|
+
interface IndexingConfig {
|
|
32
|
+
/** `domains.canonical` — the origin every default is derived from. */
|
|
33
|
+
canonical?: string | undefined;
|
|
34
|
+
/** Overrides the derived `<canonical>/sitemap-index.xml`. */
|
|
35
|
+
sitemapUrl?: string | undefined;
|
|
36
|
+
indexNow?: IndexNowConfig | undefined;
|
|
37
|
+
searchConsole?: SearchConsoleConfig | undefined;
|
|
38
|
+
/** Pages `--check` verifies. Explicit because URL Inspection is quota-bound. */
|
|
39
|
+
priorityUrls?: string[] | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The resolved environment's robots policy. A value containing `noindex` blocks
|
|
42
|
+
* the entire run — see `plan()`.
|
|
43
|
+
*/
|
|
44
|
+
robots?: string | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** One `<url>` (or `<sitemap>`) entry. */
|
|
47
|
+
interface SitemapEntry {
|
|
48
|
+
loc: string;
|
|
49
|
+
lastmod?: string;
|
|
50
|
+
}
|
|
51
|
+
/** The previous run's sitemap state, as written by `writeSnapshot`. */
|
|
52
|
+
interface Snapshot {
|
|
53
|
+
version: 1;
|
|
54
|
+
sitemapUrl: string;
|
|
55
|
+
takenAt: string;
|
|
56
|
+
/** `loc` → `lastmod`, or `''` when the entry carried none. */
|
|
57
|
+
entries: Record<string, string>;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/indexing/plan.d.ts
|
|
61
|
+
declare const INDEXNOW_ENDPOINT = "https://api.indexnow.org/indexnow";
|
|
62
|
+
/**
|
|
63
|
+
* IndexNow's own cap per submission. Batches larger than this are split rather
|
|
64
|
+
* than truncated — a silently dropped tail is the failure mode this command is
|
|
65
|
+
* meant to eliminate, not reproduce.
|
|
66
|
+
*/
|
|
67
|
+
declare const INDEXNOW_BATCH = 10000;
|
|
68
|
+
/** Why the URL set is what it is. */
|
|
69
|
+
type UrlReason = 'explicit' | 'all' | 'cold' | 'diff';
|
|
70
|
+
/** A channel's decision: on, or off with a stated reason. */
|
|
71
|
+
interface ChannelPlan {
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
/** Why it's off. Present iff `enabled` is false. */
|
|
74
|
+
skip?: string;
|
|
75
|
+
}
|
|
76
|
+
interface IndexNowPlan extends ChannelPlan {
|
|
77
|
+
endpoint: string;
|
|
78
|
+
keyLocation?: string | undefined;
|
|
79
|
+
key?: string | undefined;
|
|
80
|
+
host?: string | undefined;
|
|
81
|
+
/** URL batches, each within `INDEXNOW_BATCH`. */
|
|
82
|
+
batches: string[][];
|
|
83
|
+
}
|
|
84
|
+
interface SearchConsolePlan extends ChannelPlan {
|
|
85
|
+
siteUrl?: string | undefined;
|
|
86
|
+
}
|
|
87
|
+
interface IndexingPlan {
|
|
88
|
+
/** Set when the whole run is refused. Nothing is submitted. */
|
|
89
|
+
blocked?: string;
|
|
90
|
+
/** Always computed; `undefined` when nothing in the config resolves to one. */
|
|
91
|
+
sitemapUrl: string | undefined;
|
|
92
|
+
/** URLs that changed and should be submitted. */
|
|
93
|
+
urls: string[];
|
|
94
|
+
reason: UrlReason;
|
|
95
|
+
/** No usable previous snapshot — `urls` is everything. */
|
|
96
|
+
cold: boolean;
|
|
97
|
+
indexNow: IndexNowPlan;
|
|
98
|
+
searchConsole: SearchConsolePlan;
|
|
99
|
+
/** URLs `--check` will inspect. */
|
|
100
|
+
check: string[];
|
|
101
|
+
/** Things the operator needs to read. Printed by `--dry` and by a real run. */
|
|
102
|
+
notes: string[];
|
|
103
|
+
}
|
|
104
|
+
interface PlanInput {
|
|
105
|
+
config: IndexingConfig;
|
|
106
|
+
/** The sitemap as it stands now. */
|
|
107
|
+
current: SitemapEntry[];
|
|
108
|
+
/** The previous run's state, if any. */
|
|
109
|
+
previous?: Snapshot | undefined;
|
|
110
|
+
/** `--urls`: submit exactly these, skipping the diff. */
|
|
111
|
+
explicitUrls?: string[] | undefined;
|
|
112
|
+
/** `--all`: submit every URL in the sitemap. */
|
|
113
|
+
all?: boolean | undefined;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the sitemap URL.
|
|
117
|
+
*
|
|
118
|
+
* `sitemap-index.xml` is @astrojs/sitemap's default filename and is what
|
|
119
|
+
* `vitops({ sitemap })` registers, so it is the right default here. It resolves to
|
|
120
|
+
* a flat `<urlset>` on a small site, which `parseSitemap` handles by reading the
|
|
121
|
+
* root element rather than the name.
|
|
122
|
+
*/
|
|
123
|
+
declare function resolveSitemapUrl(config: IndexingConfig): string | undefined;
|
|
124
|
+
/** Resolve the IndexNow key file URL. */
|
|
125
|
+
declare function resolveKeyLocation(config: IndexingConfig): string | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Decide what this run should do.
|
|
128
|
+
*
|
|
129
|
+
* Ordering of the URL-selection rules matters and is not arbitrary: an explicit
|
|
130
|
+
* `--urls` is an operator overriding the tool, `--all` is an operator overriding
|
|
131
|
+
* the diff, a cold snapshot is the tool having no basis to narrow, and the diff is
|
|
132
|
+
* the normal path. Each is reported in `reason` so `--dry` explains itself.
|
|
133
|
+
*/
|
|
134
|
+
declare function plan(input: PlanInput): IndexingPlan;
|
|
135
|
+
/** Render a plan as the human-readable block `--dry` prints. */
|
|
136
|
+
declare function formatPlan(p: IndexingPlan): string;
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/indexing/sitemap.d.ts
|
|
139
|
+
/** What a sitemap document turned out to be. */
|
|
140
|
+
interface ParsedSitemap {
|
|
141
|
+
/** `<url>` entries, when this was a `<urlset>`. */
|
|
142
|
+
entries: SitemapEntry[];
|
|
143
|
+
/** Child sitemap URLs, when this was a `<sitemapindex>`. */
|
|
144
|
+
sitemaps: string[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Parse a sitemap or sitemap index.
|
|
148
|
+
*
|
|
149
|
+
* Which one it is comes from the root element, not from the filename — an
|
|
150
|
+
* `sitemap-index.xml` that is actually a flat `<urlset>` is common enough
|
|
151
|
+
* (@astrojs/sitemap emits an index only past `entryLimit`) that keying on the name
|
|
152
|
+
* would misread the single-file case as "no URLs".
|
|
153
|
+
*/
|
|
154
|
+
declare function parseSitemap(xml: string): ParsedSitemap;
|
|
155
|
+
/** Reads a sitemap from a URL or a local path. */
|
|
156
|
+
type SitemapReader = (source: string) => Promise<string>;
|
|
157
|
+
/**
|
|
158
|
+
* Resolve a sitemap to its full entry list, following an index one level down.
|
|
159
|
+
*
|
|
160
|
+
* One level is the whole spec — the sitemaps protocol forbids an index pointing at
|
|
161
|
+
* another index — so recursing further would only chase a malformed document.
|
|
162
|
+
* Child fetches run sequentially: a large site's index can list dozens of files,
|
|
163
|
+
* and hammering the consumer's own origin to save a second is a poor trade.
|
|
164
|
+
*/
|
|
165
|
+
declare function collectEntries(source: string, read: SitemapReader): Promise<{
|
|
166
|
+
entries: SitemapEntry[];
|
|
167
|
+
sources: string[];
|
|
168
|
+
}>;
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/indexing/snapshot.d.ts
|
|
171
|
+
/** Default location, relative to the consumer's project root. */
|
|
172
|
+
declare const SNAPSHOT_PATH = ".vitops/sitemap-snapshot.json";
|
|
173
|
+
/**
|
|
174
|
+
* Read a snapshot, or `undefined` when there isn't a usable one.
|
|
175
|
+
*
|
|
176
|
+
* A corrupt or wrong-version file reads as absent, not as empty. The distinction
|
|
177
|
+
* matters: "absent" submits everything, whereas an empty entry map would look like
|
|
178
|
+
* a site that legitimately has no URLs and submit nothing — the silent-success
|
|
179
|
+
* failure this whole command exists to remove.
|
|
180
|
+
*/
|
|
181
|
+
declare function readSnapshot(path: string): Snapshot | undefined;
|
|
182
|
+
/** Build a snapshot from the entries just read. */
|
|
183
|
+
declare function toSnapshot(sitemapUrl: string, entries: SitemapEntry[], takenAt: string): Snapshot;
|
|
184
|
+
/**
|
|
185
|
+
* Write the snapshot, creating its directory.
|
|
186
|
+
*
|
|
187
|
+
* Call this only after a submission actually succeeded. Writing it eagerly would
|
|
188
|
+
* record URLs as notified that never were, and because the next run diffs against
|
|
189
|
+
* it, those URLs would never be submitted again — a single transient 503 would
|
|
190
|
+
* silently drop a page from every future run.
|
|
191
|
+
*/
|
|
192
|
+
declare function writeSnapshot(path: string, snapshot: Snapshot): void;
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/indexing/indexnow.d.ts
|
|
195
|
+
/** Generate a key. 32 hex characters, the conventional shape. */
|
|
196
|
+
declare function newKey(): string;
|
|
197
|
+
/** The body of the key file served at `keyLocation`: the key, and nothing else. */
|
|
198
|
+
declare const keyFileContents: (key: string) => string;
|
|
199
|
+
interface SubmitResult {
|
|
200
|
+
ok: boolean;
|
|
201
|
+
status: number;
|
|
202
|
+
urls: number;
|
|
203
|
+
/** The endpoint's own explanation, when it gave one. */
|
|
204
|
+
message?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Verify the key file is live and correct before submitting.
|
|
208
|
+
*
|
|
209
|
+
* Worth a request of its own because of how IndexNow fails: a submission with an
|
|
210
|
+
* unreachable key file returns `403`, but one with a *reachable* key file whose
|
|
211
|
+
* contents are stale is accepted with `202` and then silently discarded. Checking
|
|
212
|
+
* first is the only way to tell "submitted" from "submitted and ignored".
|
|
213
|
+
*/
|
|
214
|
+
declare function verifyKeyFile(keyLocation: string, key: string, fetchImpl?: typeof fetch): Promise<{
|
|
215
|
+
ok: boolean;
|
|
216
|
+
reason?: string;
|
|
217
|
+
}>;
|
|
218
|
+
/**
|
|
219
|
+
* Submit one batch. Callers iterate `plan.batches` — the split is the planner's
|
|
220
|
+
* job, so that what gets sent is decided in the pure layer and visible in `--dry`.
|
|
221
|
+
*/
|
|
222
|
+
declare function submitBatch(plan: IndexNowPlan, urls: string[], fetchImpl?: typeof fetch): Promise<SubmitResult>;
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/indexing/gsc.d.ts
|
|
225
|
+
/** The fields this module needs from a service-account JSON key. */
|
|
226
|
+
interface ServiceAccount {
|
|
227
|
+
client_email: string;
|
|
228
|
+
private_key: string;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Parse a service-account credential from its two supported sources.
|
|
232
|
+
*
|
|
233
|
+
* `VITOPS_GSC_SERVICE_ACCOUNT` holds the JSON inline (what a CI secret store
|
|
234
|
+
* gives you); `GOOGLE_APPLICATION_CREDENTIALS` holds a path (Google's own
|
|
235
|
+
* convention, and what a local `gcloud` setup already has). Supporting both means
|
|
236
|
+
* neither environment has to be reshaped to fit the tool.
|
|
237
|
+
*/
|
|
238
|
+
declare function parseServiceAccount(raw: string): ServiceAccount;
|
|
239
|
+
/**
|
|
240
|
+
* Exchange a service-account key for an access token (RS256 JWT bearer flow).
|
|
241
|
+
*
|
|
242
|
+
* `now` is a parameter so this is testable without mocking the clock.
|
|
243
|
+
*/
|
|
244
|
+
declare function getAccessToken(account: ServiceAccount, fetchImpl?: typeof fetch, now?: number): Promise<string>;
|
|
245
|
+
/**
|
|
246
|
+
* Re-submit a sitemap — the automated form of the manual resubmit in the UI.
|
|
247
|
+
*
|
|
248
|
+
* A `403` here almost always means `siteUrl` doesn't match the property exactly
|
|
249
|
+
* (`sc-domain:acme.ca` vs `https://acme.ca/`) or the service account isn't an
|
|
250
|
+
* owner of it, rather than that the sitemap is bad.
|
|
251
|
+
*/
|
|
252
|
+
declare function submitSitemap(token: string, siteUrl: string, sitemapUrl: string, fetchImpl?: typeof fetch): Promise<{
|
|
253
|
+
ok: boolean;
|
|
254
|
+
status: number;
|
|
255
|
+
message?: string;
|
|
256
|
+
}>;
|
|
257
|
+
/** What the URL Inspection API said about one page. */
|
|
258
|
+
interface InspectionResult {
|
|
259
|
+
url: string;
|
|
260
|
+
/** Google's verdict, e.g. `PASS`, `NEUTRAL`, `FAIL`. */
|
|
261
|
+
verdict?: string;
|
|
262
|
+
/** e.g. `INDEXING_ALLOWED`, `BLOCKED_BY_ROBOTS_TXT`. */
|
|
263
|
+
coverageState?: string;
|
|
264
|
+
lastCrawlTime?: string;
|
|
265
|
+
/** True when Google reports the page as indexed. */
|
|
266
|
+
indexed: boolean;
|
|
267
|
+
error?: string;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Inspect one URL. **Read-only** — this reports what Google did, it cannot ask for
|
|
271
|
+
* anything. Quota is 2000 queries/day per property, which is why the caller works
|
|
272
|
+
* from an explicit `priorityUrls` list rather than the whole sitemap.
|
|
273
|
+
*/
|
|
274
|
+
declare function inspectUrl(token: string, siteUrl: string, inspectionUrl: string, fetchImpl?: typeof fetch): Promise<InspectionResult>;
|
|
275
|
+
//#endregion
|
|
276
|
+
export { type ChannelPlan, INDEXNOW_BATCH, INDEXNOW_ENDPOINT, type IndexNowConfig, type IndexNowPlan, type IndexingConfig, type IndexingPlan, type InspectionResult, type ParsedSitemap, type PlanInput, SNAPSHOT_PATH, type SearchConsoleConfig, type SearchConsolePlan, type ServiceAccount, type SitemapEntry, type SitemapReader, type Snapshot, type SubmitResult, type UrlReason, collectEntries, formatPlan, getAccessToken, inspectUrl, keyFileContents, newKey, parseServiceAccount, parseSitemap, plan, readSnapshot, resolveKeyLocation, resolveSitemapUrl, submitBatch, submitSitemap, toSnapshot, verifyKeyFile, writeSnapshot };
|
|
277
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/indexing/types.ts","../../src/indexing/plan.ts","../../src/indexing/sitemap.ts","../../src/indexing/snapshot.ts","../../src/indexing/indexnow.ts","../../src/indexing/gsc.ts"],"mappings":"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"}
|