@getvitops/cli 4.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,155 @@
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
+
3
153
  ## 4.0.0
4
154
 
5
155
  ### Major Changes