@getvitops/cli 3.0.0 → 4.1.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,275 @@
1
1
  # @getvitops/cli
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2c890c0: Link a site to its ad properties: `site.ads`, `vitops ads`, and `<Ads />`.
8
+
9
+ `vitops search` covers the whole Search Console relationship. Nothing covered the equivalent one
10
+ with ad platforms, and a site's ad accounts had nowhere to live — so a Meta pixel pasted into a
11
+ template was invisible to the rest of the toolchain. It set `_fbp` on a site whose generated cookie
12
+ notice never mentioned it, whose consent gate never cleared it on revoke, and — for LinkedIn and
13
+ Pinterest — whose attribution never captured the click ID at all, so every conversion from those
14
+ platforms arrived indistinguishable from organic traffic.
15
+
16
+ **New `site.ads` block**, keyed by platform (`google`, `meta`, `linkedin`, `reddit`, `tiktok`,
17
+ `microsoft`, `pinterest`, `snapchat`):
18
+
19
+ ```jsonc
20
+ {
21
+ "site": {
22
+ "ads": {
23
+ "meta": { "pixelId": "123456789", "domainVerification": "abc123" },
24
+ "google": {
25
+ "accountId": "123-456-7890",
26
+ "pixelId": "AW-987654321",
27
+ "conversionLabel": "xyz",
28
+ },
29
+ },
30
+ },
31
+ }
32
+ ```
33
+
34
+ **`vitops ads setup`** ensures each platform's domain-verification DNS record. Only four platforms
35
+ verify a domain at all — Meta, TikTok, Pinterest and Snapchat, by apex DNS TXT — and that record is
36
+ the one thing created for you (in Cloudflare, via `CLOUDFLARE_API_TOKEN`; created only, never edited
37
+ or deleted). Google Ads, LinkedIn, Reddit and Microsoft Ads have no domain verification: linking
38
+ there is the tag and the account id, and the run says so rather than skipping in silence. No
39
+ platform Marketing API is called — Meta's needs a system-user token and Google's an approved
40
+ developer token with your own account on the line — so the final "Verify" click is surfaced as a
41
+ reminder. `--dry` prints the plan, `--check` reports drift and exits non-zero.
42
+
43
+ **It asks for what the config is missing.** A verification token does not exist until someone
44
+ fetches it from the platform UI, so a first run prompts for it, naming the exact UI path, folds the
45
+ answer into the plan and writes it to your config. The token is not a secret — it is published in
46
+ DNS, and the platform fetching it back is the ownership proof, exactly like the IndexNow key.
47
+ Prompting requires a TTY: with `--dry`, `--check`, `--no-prompt`, or in CI, you get a named error
48
+ instead and the run never hangs. `--no-write` keeps the answer out of the config.
49
+
50
+ **`vitops ads tags`** prints each pixel as an inert, consent-gated `<script>` — `type="text/plain"`
51
+ with the library URL on `data-src`, so an undecided visitor's page issues no third-party request.
52
+ For Bricks, WordPress, Eleventy: any stack without the Astro integration.
53
+
54
+ **`vitops ads lint`** reports the gaps that are invisible at runtime: a click ID the platform stamps
55
+ that attribution does not capture, a pixel while `site.tracking` is off, a property with no tag id.
56
+
57
+ **`<Ads />` in `@getvitops/astro`**, a sibling of `<Analytics />` rather than part of it — ad
58
+ properties come from the site config, state their own consent category (`marketing` by default,
59
+ rather than being derived), and switch per environment on their own
60
+ (`environments.<env>.ads`, defaulting to `analytics`, then true, so a preview deployment can send
61
+ pageviews without firing conversion pixels). Both components now render through one `<GatedTags />`,
62
+ so the inert markup has a single implementation.
63
+
64
+ **Your cookie notice now discloses every configured pixel** — name, cookies and opt-out — from the
65
+ same table that writes `data-consent-cookies`, so the notice and the revoke cannot disagree. If you
66
+ add `site.ads` to a config whose privacy policy or cookie notice you have already published,
67
+ re-generate and re-read them.
68
+
69
+ `li_fat_id` (LinkedIn) and `epik` (Pinterest) join the click-ID capture vocabulary; a site running
70
+ either platform starts attributing conversions it previously recorded as organic.
71
+
72
+ - e764090: Separate where a processor stores information from whose law can reach it.
73
+
74
+ A processor carried one optional `country`, and the privacy policy flat-deduped it into a single
75
+ sentence. That conflated two facts a disclosure has to keep apart — **where the data rests** and
76
+ **which jurisdiction can compel access** — and privacy law turns on the second: the OPC's concern
77
+ is foreign _access_, not merely foreign storage.
78
+
79
+ Ordinary arrangements were inexpressible. Azure is data-resident in Canada and US-controlled;
80
+ Zoho can serve mail from a Canadian datacentre and telemetry from the US while being headquartered
81
+ in India; a Microsoft 365 tenant can be Canadian with its identity data in the US. For all of
82
+ them every available option was wrong: `country: "Canada"` rendered the incoherent _"outside of
83
+ Canada, including Canada"_, naming the operator's country asserted storage that wasn't happening,
84
+ and omitting it dropped the processor from the disclosure **silently**.
85
+
86
+ **New on a processor:**
87
+
88
+ ```jsonc
89
+ {
90
+ "name": "Zoho",
91
+ "purpose": "receiving and storing email sent to us",
92
+ "storage": [
93
+ { "country": "Canada", "scope": "mail and productivity data" },
94
+ { "country": "the United States", "scope": "analytics and telemetry" },
95
+ ],
96
+ "operatorCountry": "India",
97
+ }
98
+ ```
99
+
100
+ - `storage[]` — where information rests, each entry optionally `scope`d to a category. The scope
101
+ is what makes "Canadian tenant, identity data in the US" sayable.
102
+ - `operatorCountry` — the jurisdiction that can compel the provider to produce it. Gets its own
103
+ sentence: _"…established in, or controlled from, jurisdictions outside of Canada, including
104
+ India. Personal information those providers handle on our behalf may be subject to the laws of
105
+ those jurisdictions even when it is stored in Canada."_
106
+ - `country` still works, as shorthand asserting **both** facts — which is what it always
107
+ asserted, since the sentence it fed claimed storage and legal reach in one breath.
108
+
109
+ **Combining `country` with either explicit field is rejected** by `vitops validate`. Whether the
110
+ shorthand narrows the explicit fact or adds to it are two readings that make contradictory legal
111
+ claims, so neither is guessed.
112
+
113
+ A country in the policy's own jurisdiction is no longer treated as a transfer, which is the
114
+ `country: "Canada"` fix. Comparison ignores case and a leading "the".
115
+
116
+ ### This changes text you have already published
117
+
118
+ **Cloudflare, Cloudflare Turnstile, Vercel and Netlify now assert operator jurisdiction and no
119
+ storage.** They previously claimed `the United States` into a sentence saying "stored or processed
120
+ in" — a claim about the wrong fact. Cloudflare is anycast: a request from Toronto is answered from
121
+ a Toronto PoP, and Workers/R2 have residency controls the config cannot see.
122
+
123
+ So a site whose only foreign element is its host or Turnstile **stops claiming foreign storage**
124
+ and instead discloses foreign legal reach. That is a retraction in the direction of accuracy, and
125
+ it deserves a re-read. **If you pin a US region, say so** — declare a processor with
126
+ `storage: [{ country: "the United States" }]`. This generalises the rule the provider table
127
+ already applied to Matomo: "we don't know" is a fact.
128
+
129
+ **Everything else renders identically.** Google Analytics, Tag Manager, Clarity, Plausible,
130
+ Matomo Cloud and any processor you declared with `country` produce the same words as before,
131
+ because the shorthand expands to both facts and the operator sentence is suppressed when it would
132
+ only restate a country already disclosed as storage. The one cosmetic change: the closing
133
+ "subject to access requests…" sentence is now its own paragraph, since it is shared by both
134
+ clauses.
135
+
136
+ **A processor with no location at all is now reported** on stderr by `vitops legal`, naming the
137
+ document it will be missing from. It cannot appear in a transfer disclosure — there is nothing
138
+ true to say — but it used to vanish in silence, which is the failure that looks tidy. It is not a
139
+ validation error: a bare `{ name, purpose }` is still valid.
140
+
141
+ New exports from `@getvitops/generator`: `processorsMissingLocation`, `JURISDICTION_COUNTRIES`,
142
+ and the `ProcessorStorage` type. `PolicyVars` gains `jurisdictionCountry`, `storageCountries`,
143
+ `scopedStorage` and `operatorCountries`; `countries` is **deprecated but retained**, and still
144
+ names every country it ever named, so a custom template keeps rendering what it rendered.
145
+
146
+ ### Patch Changes
147
+
148
+ - Updated dependencies [2c890c0]
149
+ - Updated dependencies [e764090]
150
+ - @getvitops/generator@4.1.0
151
+ - @getvitops/utils@4.1.0
152
+
153
+ ## 4.0.0
154
+
155
+ ### Major Changes
156
+
157
+ - f7bc0a0: Add `vitops search setup` — onboard domains into Google Search Console as domain properties.
158
+
159
+ **Breaking:** the `vitops indexing` command has been renamed to **`vitops search notify`**. There is
160
+ no alias — update any scripts or CI that call `vitops indexing` to `vitops search notify` (same flags,
161
+ same behaviour).
162
+
163
+ **New: `vitops search setup`.** For each domain in a site config's new `site.searchConsole` block
164
+ (a record keyed by bare hostname), it:
165
+ - ensures the apex verification TXT record in Cloudflare,
166
+ - verifies ownership via the Site Verification API (DNS_TXT), retried with exponential backoff while
167
+ DNS propagates — a still-unverified domain is reported **PENDING**, not failed,
168
+ - adds the `sc-domain:` property via the Search Console API,
169
+ - adds any `delegatedOwners` to the verified Site Verification web resource.
170
+
171
+ It is idempotent (a re-run of an onboarded domain is a no-op), supports `--check` (report drift, exit
172
+ non-zero, mutate nothing) and `--dry` (print the plan, change nothing), and `--domain <name>` to
173
+ scope to one entry. DNS records are only ever created, never edited or deleted.
174
+
175
+ Credentials come from the environment, never the config: `CLOUDFLARE_API_TOKEN` (a `Zone:DNS:Edit`
176
+ token) and a Google **user OAuth refresh token** as `VITOPS_GOOGLE_CLIENT_ID` /
177
+ `VITOPS_GOOGLE_CLIENT_SECRET` / `VITOPS_GOOGLE_REFRESH_TOKEN` (scoped to `siteverification` +
178
+ `webmasters`). Granting a Google Group **Full-User** access has no Search Console API and is surfaced
179
+ as a reminder in the summary rather than automated.
180
+
181
+ - ceed51f: `vitops search notify` now accepts either Google credential, so most sites need only one.
182
+
183
+ The two halves of `vitops search` each demanded a different, unrelated Google setup — five environment variables between them. `search setup` requires a user OAuth credential (verifying a site makes the caller an **owner** of the property, and that should be a person, not a project robot), while `search notify` accepted only a service account. Search Console does not care which identity calls it, so a consumer who had already run `search setup` was being made to create a second credential for the other half of the same command.
184
+
185
+ `search notify` now takes whichever you have, preferring the service account when both are set — it runs on every deploy, in CI, and a service account does not expire, whereas a refresh token can be revoked and expires after 7 days for an OAuth client still in _Testing_ publishing status. `search setup` is unchanged and still requires user OAuth.
186
+
187
+ **Breaking — two renamed exports.** Both did the same job under the same name from different subpaths, which forced an alias at every call site:
188
+ - `@getvitops/utils/indexing`: `getAccessToken` → **`serviceAccountToken`**
189
+ - `@getvitops/utils/onboarding`: `getAccessToken` → **`refreshTokenGrant`**
190
+
191
+ New: `googleAccessToken(credential)` from `@getvitops/utils/indexing` takes a `GoogleCredential` union and is what both wrappers now call. If you were importing either `getAccessToken`, switch to the specific name, or to `googleAccessToken` if you want to accept either identity.
192
+
193
+ Internally the exchange is now one function. The two grants send different form bodies; everything after that — endpoint, content-type, error handling, `access_token` extraction — was duplicated verbatim in two modules. The service-account JWT signing had no test at all, because it was only reachable over the network; it now does, including the literal-`\n`-in-a-PEM case that secret stores produce.
194
+
195
+ ### Minor Changes
196
+
197
+ - 14813fa: Document which tier provides each pattern — `vitops docs components`.
198
+
199
+ The toolkit ships four tiers that compose: CSS framework classes, `<wc-*>` web components, Astro
200
+ components, Bricks elements. Nothing said so. The class reference listed pattern names, the
201
+ elements reference listed Bricks controls, and the fact that `tree` is _also_ a web component
202
+ _and_ an Astro component existed only in the source layout. The cost is silent: you hand-write
203
+ the markup a component already emits, or you wrap a component that already emits its own tag.
204
+
205
+ `TIERS` in `@getvitops/generator` is the new manifest — per pattern, its CSS partial and
206
+ representative classes, its `wc-*` tag and which bundle that ships in, its Astro component(s),
207
+ its Bricks element, and one line saying what to actually write. `tierPatterns(tier)` projects one
208
+ tier. `vitops docs components` renders all four in one document, and the reference sections it
209
+ feeds render the same data per tier.
210
+
211
+ It is **authored rather than derived, and drift-guarded** instead. Naming convention carries most
212
+ of it and breaks exactly where it matters — `splitter.css` hosts two components, `tag.css` serves
213
+ three config patterns, `anchor-link.css` provides `.link`, `layout.css` provides `.split` and
214
+ `.centered` — so a derivation would need an exceptions table longer than the rule, and a rename
215
+ would silently drop a link. Instead, tests fail the build when a hand-written CSS partial, a
216
+ `customElements.define`, an exported Astro component or a Bricks element isn't accounted for.
217
+
218
+ Four things the manifest records that cannot be inferred, each a real failure mode:
219
+ - **Whether an Astro component wraps a web component.** `Tree` and `CookieConsent` emit the
220
+ `<wc-*>` tag with the fallback inside; `Details`, `Drawer`, `Popover`, `NavShell` and `Subgrid`
221
+ emit tier-1 markup with no web component at all. Reading the first group as the second gives
222
+ `<wc-tree><Tree /></wc-tree>` — two elements on one tree, which renders and misbehaves.
223
+ - **Where each element ships.** `elements.js` for the registered set, its own bundle for
224
+ `<wc-consent>` and `<wc-theme-editor>`, and **no bundle at all** for the four editor-v2 tags —
225
+ which are registered, importable-looking and inert in a consumer project. The guard checks this
226
+ against what `js/elements.ts` imports, so it can't claim an element ships when it doesn't.
227
+ - **Whether a pattern is config-authored.** `patterns.items` patterns get the token cascade,
228
+ states, role variants and override hooks; a structural partial gets none of them. The docs also
229
+ now say when a config-authored pattern is **absent from your config**, because naming its
230
+ classes while your build emits none of them is worse than omitting it.
231
+ - **The import specifier, verbatim.** These are published as the line to copy, so they are checked
232
+ against `@getvitops/astro`'s `exports`. That guard immediately caught
233
+ `@getvitops/astro/components/../CookieConsent.astro` — plausible-looking, right basename, not an
234
+ importable path (`CookieConsent.astro` ships from the package root).
235
+
236
+ - 14813fa: Document the consent, conversion-tracking, search and legal subsystems for agents.
237
+
238
+ `vitops docs` gains four topics — `consent`, `tracking`, `search`, `legal` — and the docs
239
+ bundle gains the matching concept docs under `concepts/`. Until now these subsystems were
240
+ described only by their generated config-schema field descriptions, so an agent working in a
241
+ consumer project had no account of the rules that make them work, and every one of those rules
242
+ fails **silently** when broken:
243
+ - a gated tag given a live `src` instead of `type="text/plain"` fetches the third party anyway,
244
+ so the gate becomes decorative;
245
+ - a caller using `granted()` instead of `require()` never raises the banner, so on a site where
246
+ nothing else demands that category the permission is never offered, never granted, and the
247
+ write never happens;
248
+ - an absent `window.vitopsConsent` read as "denied" rather than "this site has no gate";
249
+ - a consent patch widened to every category records consent nobody gave;
250
+ - a sitemap with no `<lastmod>` makes edited pages undetectable, so `search notify` looks
251
+ healthy while never resubmitting anything;
252
+ - a stale IndexNow key file returns `202` and is then discarded;
253
+ - Google's Indexing API accepts ordinary URLs, discards them, and violates its own terms.
254
+
255
+ The packaged agent skill now covers consent and conversion tracking, and its description names
256
+ these subsystems so it is actually surfaced when they are the task.
257
+
258
+ Also fixed: the icons concept doc still called the Astro integration `getvitops()`, renamed to
259
+ `vitops()` in 3.0.
260
+
261
+ ### Patch Changes
262
+
263
+ - Updated dependencies [14813fa]
264
+ - Updated dependencies [c6b99e7]
265
+ - Updated dependencies [c6b99e7]
266
+ - Updated dependencies [f7bc0a0]
267
+ - Updated dependencies [ceed51f]
268
+ - Updated dependencies [14813fa]
269
+ - Updated dependencies [14813fa]
270
+ - @getvitops/generator@4.0.0
271
+ - @getvitops/utils@4.0.0
272
+
3
273
  ## 3.0.0
4
274
 
5
275
  ### Major Changes
package/README.md CHANGED
@@ -25,7 +25,7 @@ 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 |
28
+ | `vitops search notify [--dry] [--check]` | tell search engines about a deploy |
29
29
  | `vitops media [--raw <dir>] [-o <dir>]` | encode raw video to WebM + MP4 + poster |
30
30
 
31
31
  Every `--input` takes a `design-system.json` **or** the larger site config that embeds one