@getvitops/cli 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 CHANGED
@@ -1,5 +1,211 @@
1
1
  # @getvitops/cli
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
+ ### Patch Changes
82
+
83
+ - Updated dependencies [6e68ace]
84
+ - Updated dependencies [b115993]
85
+ - @getvitops/generator@3.0.0
86
+ - @getvitops/utils@3.0.0
87
+
88
+ ## 2.1.0
89
+
90
+ ### Minor Changes
91
+
92
+ - 20e518e: Add `vitops indexing` — tell search engines about a deploy, instead of opening Search Console by hand.
93
+
94
+ Configure it in your site config under `seo.indexing`, then run it after deploying:
95
+
96
+ ```jsonc
97
+ "seo": {
98
+ "indexing": {
99
+ "indexNow": { "key": "…" },
100
+ "searchConsole": { "siteUrl": "sc-domain:acme.ca" },
101
+ "priorityUrls": ["https://acme.ca/", "https://acme.ca/services"]
102
+ }
103
+ }
104
+ ```
105
+
106
+ ```
107
+ vitops indexing --dry # print the plan, make no requests
108
+ vitops indexing # submit
109
+ vitops indexing --check # a day or two later: did Google actually index them?
110
+ ```
111
+
112
+ It reads your sitemap, diffs it against the previous run, and submits only what changed —
113
+ pinging **IndexNow** and re-submitting your sitemap through the **Search Console API**.
114
+ `--check` inspects `priorityUrls` and exits non-zero on a page Google hasn't indexed, so a
115
+ scheduled CI job can catch a page that quietly fell out of the index.
116
+
117
+ **Be clear-eyed about what this can do.** Google exposes no API that requests indexing — the
118
+ button in Search Console isn't available anywhere, URL Inspection is read-only, and the sitemap
119
+ ping endpoint was removed in 2023. IndexNow reaches Bing, Yandex, Naver, Seznam and Yep; Google
120
+ doesn't participate. So this automates every sanctioned step and then verifies the outcome; it
121
+ does not make Google re-index on demand, and nothing can. Google's Indexing API is deliberately
122
+ not wired: it's scoped to job postings and livestreams, and using it for ordinary pages violates
123
+ its terms.
124
+
125
+ Also new, and worth wiring at the same time — **`gitLastmod()` for real sitemap dates**:
126
+
127
+ ```js
128
+ import vitops, { gitLastmod } from '@getvitops/astro';
129
+ vitops({ sitemap: { serialize: await gitLastmod() } });
130
+ ```
131
+
132
+ `@astrojs/sitemap` emits no `<lastmod>` by default, which means a crawler is told your pages
133
+ exist but never that one changed — and it's what lets `vitops indexing` submit a handful of URLs instead
134
+ of all of them. `gitLastmod()` derives each date from the source file's last commit, and leaves a
135
+ page alone rather than guessing (dynamic routes, ambiguous slugs, shallow clones): an inaccurate
136
+ `lastmod` is worse than none, because Google stops trusting the field site-wide.
137
+
138
+ Requires `fetch-depth: 0` in CI — the default shallow clone has no history to read, and it warns
139
+ when that's the case.
140
+
141
+ **Credentials.** IndexNow's key is public by design (it's served at `/<key>.txt` as the ownership
142
+ proof), so it lives in your config, and the Astro integration writes the file into `public/` for
143
+ you — `vitops indexing --new-key` generates one, `--write-key <dir>` writes it for non-Astro stacks.
144
+ Search Console needs a service account in `VITOPS_GSC_SERVICE_ACCOUNT` or
145
+ `GOOGLE_APPLICATION_CREDENTIALS`, added as an owner of the property; it's never read from the
146
+ config file.
147
+
148
+ Persist `.vitops/` between runs (a CI cache) — that's the changed-URL state, and without it every
149
+ run submits everything.
150
+
151
+ An environment whose `robots` policy says `noindex` is refused outright, so pointing this at
152
+ staging can't publish it to a search engine.
153
+
154
+ - 9bf975a: Add `vitops media` — encode raw video into web-ready outputs, instead of hand-rolling an ffmpeg
155
+ script per project.
156
+
157
+ Keep unprocessed video in a `raw/` directory and run:
158
+
159
+ ```
160
+ vitops media --raw raw --out src/assets/processed
161
+ ```
162
+
163
+ Each source becomes three files: **VP9/WebM**, an **H.264/MP4** fallback, and a **JPG poster**.
164
+ Import them like any other asset, so your bundler content-hashes them:
165
+
166
+ ```astro
167
+ ---
168
+ import hero from '../assets/processed/hero.webm';
169
+ import poster from '../assets/processed/hero.jpg';
170
+ ---
171
+ <video poster={poster.src} autoplay muted loop playsinline>
172
+ <source src={hero} type="video/webm" />
173
+ </video>
174
+ ```
175
+
176
+ In Astro it's an integration option — `vitops({ css, media: { raw: 'raw', out: 'src/assets/processed' } })`
177
+ — and it runs in the same pass that generates your CSS. `@getvitops/vite` gains a matching `media`
178
+ option, and `@getvitops/utils/media` exports `processMedia()` for anything else.
179
+
180
+ Defaults: capped at 1920px wide, CRF 32, audio dropped (the common case is a muted autoplay loop),
181
+ poster from frame 0. All of them are flags. `--dry` prints exactly what a run would do.
182
+
183
+ **Runs are cached**, on source content plus encode settings, in `.vitops/media-manifest.json` — a
184
+ 24 MB clip that took 88 seconds the first time takes 0.14 seconds the second. A missing output
185
+ re-encodes; a corrupt manifest re-encodes everything. Neither ever reads as "already done", because
186
+ that failure is silent and no rebuild would fix it.
187
+
188
+ **Commit the outputs and the manifest.** A fresh CI clone has neither and would re-encode from
189
+ scratch; committing both means CI never needs ffmpeg. It also keeps your history clean — ffmpeg
190
+ output isn't byte-reproducible across versions, so a CI re-encode would rewrite every video on any
191
+ toolchain bump. Use `--force` when you mean to re-encode.
192
+
193
+ **`ffmpeg` is an external tool, not an npm dependency** — install it yourself (`brew install
194
+ ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails without it rather
195
+ than skipping: a page referencing a video that was never encoded is broken, not degraded.
196
+
197
+ Two defaults worth knowing about. The MP4 exists because older iOS and the in-app webviews inside
198
+ social apps still don't decode VP9, and it's written with `+faststart` — without which a browser
199
+ downloads the whole file before showing frame one. And a poster taken from frame 0 is often black
200
+ on a clip that fades in; that's `--poster-time`, not a bug.
201
+
202
+ ### Patch Changes
203
+
204
+ - Updated dependencies [20e518e]
205
+ - Updated dependencies [9bf975a]
206
+ - @getvitops/generator@2.1.0
207
+ - @getvitops/utils@2.1.0
208
+
3
209
  ## 2.0.0
4
210
 
5
211
  ### Minor Changes
package/README.md CHANGED
@@ -25,6 +25,8 @@ npx vitops generate --format bricks,css --out dist
25
25
  | `vitops favicon -i <svg\|png> -o <dir>` | generate a favicon set from a source image |
26
26
  | `vitops agents [-o AGENTS.md] [--docs-dir <dir>]` | link the agent skill + AGENTS.md pointer |
27
27
  | `vitops docs [topic] [--all]` | print live reference docs to stdout |
28
+ | `vitops indexing [--dry] [--check]` | tell search engines about a deploy |
29
+ | `vitops media [--raw <dir>] [-o <dir>]` | encode raw video to WebM + MP4 + poster |
28
30
 
29
31
  Every `--input` takes a `design-system.json` **or** the larger site config that embeds one
30
32
  (`company.json` / `site.json`) — told apart by shape, so name the file whatever you like. Add
@@ -98,6 +100,51 @@ vitops generate --input design-system.json --format design --out .
98
100
  whenever your stylesheet goes somewhere other than the repo root. Regenerate it with your CSS; it's
99
101
  derived from the same config, so it can't drift from what the browser gets.
100
102
 
103
+ ## Video (`vitops media`)
104
+
105
+ Keep unprocessed video in a `raw/` directory and encode it into web-ready outputs:
106
+
107
+ ```sh
108
+ vitops media --raw raw --out src/assets/processed
109
+ ```
110
+
111
+ Each source becomes three files — VP9/WebM, an H.264/MP4 fallback, and a JPG poster frame:
112
+
113
+ ```astro
114
+ ---
115
+ import hero from '../assets/processed/hero.webm';
116
+ import poster from '../assets/processed/hero.jpg';
117
+ ---
118
+ <video poster={poster.src} autoplay muted loop playsinline>
119
+ <source src={hero} type="video/webm" />
120
+ </video>
121
+ ```
122
+
123
+ Outputs land under `src/` so your bundler content-hashes them and they can be served immutable,
124
+ while the raw sources stay outside anything it scans.
125
+
126
+ Defaults: capped at 1920px wide, CRF 32, audio dropped (the common case is a muted autoplay loop),
127
+ poster taken from frame 0. Every one is a flag — `--max-width`, `--crf`, `--audio`,
128
+ `--poster-time`, `--max-bitrate`, `--outputs`. `vitops media --dry` prints exactly what a run
129
+ would do without encoding anything.
130
+
131
+ **Needs `ffmpeg` on `PATH`.** It's an external tool, not an npm dependency — install it yourself
132
+ (`brew install ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails
133
+ rather than skipping: a page referencing a video that was never encoded is broken, not degraded.
134
+
135
+ **Commit the outputs and `.vitops/media-manifest.json`.** Runs are cached on source content plus
136
+ encode settings, so a second run costs a hash per file instead of minutes — but a fresh CI clone
137
+ has neither, and would re-encode everything. Committing both means CI never needs ffmpeg at all.
138
+ It also keeps your history clean: ffmpeg output isn't reproducible across versions, so a CI
139
+ re-encode would rewrite every video on any toolchain bump. Use `--force` when you mean to
140
+ re-encode, and commit the result.
141
+
142
+ Two things worth knowing before you conclude something is broken:
143
+
144
+ - **Frame 0 is often black** on a clip that fades in. That's `--poster-time`, not a bug.
145
+ - **A missing output re-encodes, a corrupt manifest re-encodes everything.** Neither ever reads as
146
+ "already done" — the failure that would cause is silent, and a rebuild would never fix it.
147
+
101
148
  ## Teaching AI agents (`vitops agents` + `vitops docs`)
102
149
 
103
150
  This package **ships an agent skill** (`skill/SKILL.md`, the [Agent Skills](https://code.claude.com/docs/en/skills)