@getvitops/astro 6.0.0 → 8.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,288 @@
1
1
  # @getvitops/astro
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Rebuild the lightbox on one dialog + the existing carousel, fixing the flicker on
8
+ prev/next and the stutter on open.
9
+
10
+ **Breaking (`css`/`bricks` and `tailwind` formats — `.lightbox-dialog`'s markup
11
+ contract changed):**
12
+
13
+ - `<Gallery />` now emits ONE `<dialog class="lightbox-dialog">` per gallery,
14
+ wrapping a `<Carousel />`, instead of one dialog per image. A `.gallery__trigger`
15
+ is now a plain `<a href="#slide-id">`, not a `command="show-modal"` button.
16
+ - `.lightbox-dialog__content` / `__image` / `__nav` / `__nav-button` /
17
+ `__caption` are gone. The dialog's child is `.lightbox-dialog__track` (a
18
+ `.carousel`) — prev/next, dots, arrow keys and swipe are the carousel's,
19
+ reused rather than reimplemented. `.lightbox-dialog__close` is unchanged.
20
+ - `<wc-gallery>` no longer builds nav buttons or steps between images — the
21
+ nested `<wc-carousel>` does. It fires `gallery-open` (unchanged shape) and
22
+ re-emits the carousel's own stepping as `gallery-change`.
23
+ - `packages/core/src/web-components/utils/gallery.ts` dropped `keyToAction` and
24
+ `swipeAction` (the carousel's own key/swipe handling covers both now).
25
+ `stepIndex`/`preloadTargets` are unchanged.
26
+
27
+ **Why:** stepping used to close one `<dialog>` and open another, which made
28
+ `animation.css`'s top-layer driver play a full exit-then-entry transition on
29
+ every step — visible as a flicker on prev/next, by construction, not a bug to
30
+ tune away. Opening also awaited the full image's `decode()` before starting the
31
+ view-transition morph, so the dialog visibly stalled on a slow network. One
32
+ dialog containing one carousel makes the first failure mode unrepresentable
33
+ (stepping is a scroll, not a close/open pair) and removes the wait for the
34
+ second (the morph starts immediately, painting the thumbnail as a CSS
35
+ background placeholder until the full image arrives).
36
+
37
+ **Also improved, not just fixed:** the no-JS fallback is no longer a _stripped_
38
+ lightbox — it's a genuine one. Each trigger is ordinary fragment navigation
39
+ (`:has(:target)` in `lightbox.css` reveals the dialog), which needs no Invoker
40
+ Commands polyfill in any engine, and it now has working prev/next with no
41
+ JavaScript at all — the previous per-dialog fallback had no way to express that.
42
+
43
+ **Migration:** a consumer using `<Gallery />` needs no changes — the component's
44
+ props are unchanged. Anyone who hand-authored `.lightbox-dialog` markup (per
45
+ `docs/concepts/patterns.md` / `docs/css/classes.md`) must move to the new single-
46
+ dialog-wrapping-a-carousel shape; see `patterns.astro`'s "Gallery, hand-authored"
47
+ demo for the reference markup. `<wc-gallery>` consumers listening for
48
+ `gallery-change` keep the same event shape (`{ index, total }`).
49
+
50
+ ### Minor Changes
51
+
52
+ - Add Google Business Profile reviews: fetch, cache, render.
53
+
54
+ - **`vitops reviews`** (new CLI command) fetches a client's GBP reviews (legacy v4 API — the only
55
+ one that has them) and writes them to `.vitops/reviews.json`, meant to be committed so a build
56
+ never needs a live Google credential. `--dry` prints the plan; `--check` diffs against the cache
57
+ without writing. Requires the same `VITOPS_GOOGLE_CLIENT_ID`/`_CLIENT_SECRET`/`_REFRESH_TOKEN` (or
58
+ ADC login) `search setup` uses, scoped to `business.manage` — **and the calling GCP project must
59
+ be allowlisted by Google first**, a separate one-time approval from every other Google API here.
60
+ - **`site.reviews`** (config): which `organization.locations` to fetch, and per-location
61
+ `featured`/`minRating`/`sort`/`limit` selection. `schema` (JSON-LD) and `avatars` both default to
62
+ **off** — see the migration notes below.
63
+ - **`@getvitops/utils/reviews`** (new subpath): the pure plan/select/merge functions plus the GBP
64
+ fetcher and cache reader/writer, mirroring `./indexing`'s pure/IO split.
65
+ - **`<ReviewCard>`** (new Astro component, `@getvitops/astro`): one review card from a cache entry.
66
+ No web component — nothing here needs progressive enhancement. Composes the existing
67
+ `schemas/Review.astro` for JSON-LD only when both `schema` and `itemReviewed` are passed.
68
+ - **`<Marquee>`** (new Astro component): the tier-3 wrapper `wc-marquee`/`marquee.css` was missing.
69
+ - **`.review` pattern** (`css`/`bricks` and `tailwind` formats): a new config-generated
70
+ `patterns.items.review` entry plus `packages/core/css/patterns/review.css` for the structural
71
+ parts (rating, identity, body, reply, source link). Two new semantic icon names, `star` and
72
+ `star-half`, across every icon set.
73
+
74
+ **Migration:** no breaking change. A site adding `site.reviews` should read the "Google reviews"
75
+ section in AGENTS.md before enabling `schema` — Google's structured-data policy treats a business
76
+ marking up reviews of itself as ineligible for review rich results and risks a manual action, which
77
+ is why it defaults off.
78
+
79
+ - Repo-wide simplification pass: de-boilerplate the CLI, dedupe shared cache/plan
80
+ primitives in `@getvitops/utils`, drop dead code, and tighten dependency hygiene.
81
+
82
+ No breaking changes. (`vitops icons`'s `--site` flag was briefly going to be renamed to
83
+ `--input` for consistency with the other `Config`-anchored commands — deferred rather than
84
+ spending a major bump on a rename with no other breaking change riding along; it stays
85
+ `--site` for now.)
86
+
87
+ **Fixed:**
88
+
89
+ - `@getvitops/core`'s 12 runtime deps (`lit`, both `@oddbird` polyfills, both
90
+ `@webcomponents` packages, `colorjs.io`, etc.) were listed as `dependencies` despite
91
+ being bundled into `dist/elements.js`/`dist/polyfills.js`/`dist/deferred.js` — a
92
+ consumer installed them twice for nothing. Moved to `devDependencies`; no change to
93
+ what ships.
94
+ - Deleted `old-css-lib/` (59 files), superseded by `packages/core/css/` since the
95
+ in-memory bundling change — no consumer-facing effect, it was not part of any
96
+ published output.
97
+ - Deleted a dead Astro module (`packages/astro/src/scripts/deferred.ts`) that had
98
+ silently diverged from `packages/core/src/js/deferred.ts` and had zero importers.
99
+
100
+ **No consumer-facing change in `@getvitops/vite`** beyond the fixed-group version bump —
101
+ task-graph and caching fixes internal to this repo's own build.
102
+
103
+ Everything else (the `cli.ts` file split into `commands/{search,ads}.ts`, shared
104
+ `json-store.ts`/`plan-format.ts` helpers in `@getvitops/utils`, a `.github/actions/setup`
105
+ composite action, restatement-comment cleanup) is internal refactoring with no change to
106
+ any published API, CLI flag, or generated output.
107
+
108
+ ### Patch Changes
109
+
110
+ - Updated dependencies
111
+ - Updated dependencies
112
+ - Updated dependencies
113
+ - Updated dependencies [a84839b]
114
+ - @getvitops/core@8.0.0
115
+ - @getvitops/generator@8.0.0
116
+ - @getvitops/utils@8.0.0
117
+ - @getvitops/vite@8.0.0
118
+
119
+ ## 7.0.0
120
+
121
+ ### Major Changes
122
+
123
+ - aad43b7: A gallery/lightbox that is a fully working modal lightbox with **zero JavaScript**, plus
124
+ `ImageMetadata` support in `<Carousel />`.
125
+
126
+ Only `@getvitops/core`, `generator` and `astro` change here. This changeset makes **no
127
+ consumer-facing change** to `utils`, `cli` or `vite` — they still ship in this release because
128
+ the six share one version, but from a different changeset (`localbusiness-schema.md`) that
129
+ touches `utils` and `generator` for an unrelated reason.
130
+
131
+ **Breaking — `css`/`bricks`/`tailwind` (all formats):** the `.lightbox` thumbnail grid is now
132
+ `.gallery`, so it composes with the dialog it opens (`.lightbox-dialog`, unchanged) rather than
133
+ sharing its name. `vitops lint --fix` cannot cover this: `movedTokens()` renames colour token
134
+ names only, and there is no class-rename mechanism in `@getvitops/cli`. Migrate by hand:
135
+
136
+ | Old | New |
137
+ | ------------------ | ----------------- |
138
+ | `.lightbox` | `.gallery` |
139
+ | `.lightbox__item` | `.gallery__item` |
140
+ | `.lightbox__thumb` | `.gallery__thumb` |
141
+
142
+ `.lightbox-dialog` and its `__content` / `__image` / `__close` / `__nav` / `__nav-button` /
143
+ `__caption` parts are unchanged.
144
+
145
+ **Added — `css`/`bricks`/`tailwind` (all formats):** `patterns/gallery.css` — the `.gallery` grid,
146
+ now on the token cascade (`--br-gallery` etc.) it only half-had before.
147
+
148
+ **Added — `wc`:** `<wc-gallery>` (`@getvitops/core/elements`). Wraps a `.gallery` grid of
149
+ `command="show-modal"` triggers and the `.lightbox-dialog`s they open. With no JS, each trigger is
150
+ a real Invoker Commands button for its own dialog — open and close both work. `<wc-gallery>` adds
151
+ what the fallback deliberately cannot contain: prev/next, arrow keys, swipe (horizontal to step,
152
+ vertical to dismiss), an announced image counter, neighbour preload, and a thumbnail → full-image
153
+ `document.startViewTransition` morph where supported. Prev/next can't exist in the fallback — a
154
+ second `show-modal` invoker inside an already-open dialog stacks a second modal rather than
155
+ replacing the first — so it is generated by the element, never authored, the same rule
156
+ `<wc-tree>`'s search field follows.
157
+
158
+ **Added — `astro`:** `<Gallery />` (`@getvitops/astro/components/Gallery.astro`) — emits a
159
+ `.gallery` grid plus one `.lightbox-dialog` per image and its own `<wc-gallery>`; do not wrap it in
160
+ one. Images take `string | ImageMetadata`, rendered through a new internal `<ResponsiveImage />`
161
+ helper: a plain string renders the same raw `<img>` every component here used before, unchanged;
162
+ an `astro:assets` import renders through `<Picture />` for AVIF/WebP derivatives.
163
+
164
+ **Changed — `astro`:** `<Carousel />`'s `CarouselSlide.src` now also accepts `ImageMetadata`,
165
+ through the same `<ResponsiveImage />` helper, plus a new `formats` prop. Every existing
166
+ string-`src` call site renders byte-identical markup — purely additive.
167
+
168
+ **Added — `wc`:** the Invoker Commands API (`<button command commandfor>`) polyfill
169
+ (`invokers-polyfill`, feature-detected via `@getvitops/core/polyfills`) — resolves the TODO
170
+ `polyfills.ts` carried since 4.0. `<wc-gallery>`'s fallback and `<Drawer />` (which already emitted
171
+ `command="show-modal"` unconditionally) both now get real cross-browser coverage rather than
172
+ depending on it being "too new to rely on yet." Fetched only in an engine that doesn't support the
173
+ API natively, so a current browser downloads none of it.
174
+
175
+ **Migration:** rename `.lightbox` → `.gallery` and `.lightbox__item`/`.lightbox__thumb` →
176
+ `.gallery__item`/`.gallery__thumb` wherever authored (see table above); everything else is
177
+ additive. Consumers using the CSS pattern with no JS need no other change — the fallback markup is
178
+ unchanged in shape, only the class names moved.
179
+
180
+ ### Minor Changes
181
+
182
+ - **Added — `utils`.** `itemListGraph()`, a pure schema.org `ItemList` JSON-LD builder — a sibling
183
+ of `breadcrumbGraph()`/`localBusinessGraph()`. Extracted from `@getvitops/astro`'s `<Carousel />`
184
+ so any consumer can build a valid `ItemList` (Google's "all-in-one page" carousel format:
185
+ `ListItem.item` carrying `@type`/`name`/`url`) for content that isn't one of the six Google
186
+ carousel rich-result types (`Article`/`Recipe`/`Movie`/`Course`/`Restaurant`/`Product`) — the
187
+ entity type is no longer constrained to that set.
188
+
189
+ **Changed — `astro`.** `<Carousel />`'s `CarouselItem['type']` now accepts any string, not just
190
+ the six carousel rich-result types — non-breaking, the union only widens. It's a thin wrapper
191
+ over `itemListGraph()` now; behaviour for existing callers is unchanged.
192
+
193
+ No consumer-facing change in this release outside `utils` and `astro` — `generator`, `cli` and
194
+ `vite` still ship in this release only because the six share one version.
195
+
196
+ **Fixed — `core` (`css`/`bricks`/`tailwind` build output, all formats).** The CSS and HTML
197
+ authored inside Lit's `css`/`html` tagged templates in `<wc-*>` component sources previously
198
+ survived JS minification verbatim — a JS minifier treats a template literal's cooked text as
199
+ opaque, so `elements.js` shipped ~690 lines of indented, unminified CSS (and the equivalent in
200
+ HTML) inside an otherwise-minified 886-line file. Two source-level build transforms now collapse
201
+ both before bundling: `postcss` + `postcss-lit` + `cssnano` for `css` templates, `minify-html-literals`
202
+ (`conservativeCollapse: true`, so whitespace between inline elements in light-DOM components never
203
+ collapses to zero) for `html` templates. `elements.js` drops from 886 lines / ~101 KB to 49 lines /
204
+ ~96 KB. A second build step deduplicates the 17 `@license` comment blocks (only 6 are textually
205
+ distinct) down to one verbatim copy of each — required by BSD-3/MIT/Apache-2.0's notice-retention
206
+ terms either way, just no longer repeated once per source file that carries it. No API change;
207
+ build-time only, and none of the added tooling (`postcss`, `postcss-lit`, `cssnano`,
208
+ `minify-html-literals`) is a runtime or published dependency.
209
+
210
+ - 7b391b3: Add `<wc-counter>` — animates a number from a start value to the value already in its fallback
211
+ text, on intersection. The stat/percentage figure in a media tile ("0 → 94%"), a KPI row, a
212
+ metrics band.
213
+
214
+ No consumer-facing change in this release outside `core`, `generator` and `astro` — `utils`,
215
+ `cli` and `vite` still ship in this release only because the six share one version.
216
+
217
+ **Added — `css`/`bricks`/`tailwind` (all formats):** `.counter` / `.counter__value` /
218
+ `.counter__presentation` (`patterns/counter.css`). A registered `--_n` custom property drives the
219
+ count via a plain CSS `@keyframes` animation — the interpolation and the easing curve are CSS's,
220
+ never duplicated in JS.
221
+
222
+ **Added — `wc` (`@getvitops/core`).** `<wc-counter>` parses the final value out of its own
223
+ `.counter__value` fallback text — never from a separate attribute, so the no-JS reading and the
224
+ animated reading can't disagree — and renders it back through `Intl.NumberFormat`, seeded from the
225
+ fallback text's own separators (a grouped `1,284` never animates through ungrouped intermediates).
226
+ `counter()` cannot do that part: no decimals, no locale grouping, which is the one thing here that
227
+ genuinely needs JavaScript.
228
+ - Triggers on intersection via a single shared `IntersectionObserver`, not on upgrade — a counter
229
+ below the fold that finished before the visitor scrolled to it would have done nothing.
230
+ - `prefers-reduced-motion: reduce` means don't animate at all, checked before the observer is even
231
+ created, with `patterns/counter.css`'s own reduced-motion query as a second belt.
232
+ - The accessible name stays the final value for the whole animation: `.counter__value` is toggled
233
+ to the framework's `.sr-only` utility while running, never hidden — `display: none`/`visibility:
234
+ hidden` would pull it out of the accessible-name computation along with the visual hiding, and a
235
+ screen reader must never be read ~60 intermediate numbers. The animated text lives in a separate
236
+ `aria-hidden` presentation span it never reaches.
237
+ - `data-from` (default `0`), `data-duration` (ms, default `1200`), `data-easing` (`linear` |
238
+ `ease-in` | `ease-out` | `float-sine` | `float-bounce` — maps onto the real
239
+ `--custom-ease-*`/`--ease-float-*` tokens, so a curve edited in `animation.css` cannot drift out
240
+ of step with this element), `data-decimals` (inferred from the fallback text when absent).
241
+ - Never leaves an intermediate value on screen as the resting state: disconnecting mid-animation
242
+ restores the fallback immediately, and it was never mutated in the first place — only the
243
+ presentation span was ever written to.
244
+
245
+ **Added — `astro`.** `<Counter value="94%" />` emits its own `<wc-counter>` — do NOT wrap it in
246
+ one, same rule as `Gallery`/`Carousel`/`Tree`. `value` is the only source of the final figure;
247
+ there is no separate `to` prop, for the same one-source-of-truth reason the element itself parses
248
+ its fallback rather than taking a duplicate attribute. `enhance={false}` emits the bare fallback
249
+ for a page that cannot run `elements.js`.
250
+
251
+ ### Patch Changes
252
+
253
+ - 2b50f9e: Extract the LocalBusiness JSON-LD builder out of `<LocalBusiness />` into a reusable
254
+ `localBusinessGraph()`, and add the location fields listing platforms (GBP, Bing Places, Apple
255
+ Business Connect) actually need.
256
+
257
+ **Added — `@getvitops/utils`.** `localBusinessGraph(options)` (+ its `LocalBusinessGraphOptions`,
258
+ `OpeningHoursSpecification` and `SpecialHoursSpecification` types) is now exported from
259
+ `@getvitops/utils`, alongside the existing `organizationGraph`/`articleGraph`/`breadcrumbGraph`/
260
+ `faqGraph` builders — same module, same shape, usable anywhere a JSON-LD graph is built outside an
261
+ Astro component.
262
+
263
+ **Added — `authoring`/`config` (`@getvitops/generator`).** Four new `organization.locations.<slug>`
264
+ fields: `hoursSpecial` (dated deviations from the recurring `hours` — holiday hours, a one-off
265
+ closure), `photos` (`ImageRefSchema[]` → JSON-LD `image`), `sameAs` (this location's own listing
266
+ URLs, distinct from `organization.sameAs`, which is the company overall), and `listings` (external
267
+ listing ids keyed by platform — `google` | `bing` | `apple` — not consumed by the generator today;
268
+ recorded so a future listings-sync command has a stable id to match against).
269
+
270
+ **Changed — `astro`.** `<LocalBusiness />`'s `Props` type is now `LocalBusinessGraphOptions`
271
+ (re-exported, not redefined) rather than a local interface that had drifted from what the builder
272
+ actually accepted. No markup or prop-name change for an existing consumer.
273
+
274
+ - Updated dependencies [c0c092b]
275
+ - Updated dependencies
276
+ - Updated dependencies [aad43b7]
277
+ - Updated dependencies [2b50f9e]
278
+ - Updated dependencies [a6a8b39]
279
+ - Updated dependencies [7b391b3]
280
+ - Updated dependencies [eb9ac79]
281
+ - @getvitops/utils@7.0.0
282
+ - @getvitops/generator@7.0.0
283
+ - @getvitops/core@7.0.0
284
+ - @getvitops/vite@7.0.0
285
+
3
286
  ## 6.0.0
4
287
 
5
288
  ### Major Changes
@@ -552,28 +835,20 @@
552
835
  {
553
836
  "designSystem": {
554
837
  "themes": {
555
- "default": {
556
- /* … */
557
- },
838
+ "default": {/* … */},
558
839
  },
559
840
  },
560
841
  "organization": {
561
842
  "name": "Acme",
562
843
  "contact": "hq",
563
844
  "locations": {
564
- "hq": {
565
- /* … */
566
- },
845
+ "hq": {/* … */},
567
846
  },
568
847
  },
569
848
  "site": {
570
849
  "defaultLocale": "en",
571
- "domains": {
572
- /* … */
573
- },
574
- "analytics": {
575
- /* … */
576
- },
850
+ "domains": {/* … */},
851
+ "analytics": {/* … */},
577
852
  },
578
853
  }
579
854
  ```
@@ -742,7 +1017,7 @@ open-fade-in"` composes slide and fade. Applied at zero specificity with identit
742
1017
  Also new, and worth wiring at the same time — **`gitLastmod()` for real sitemap dates**:
743
1018
 
744
1019
  ```js
745
- import vitops, { gitLastmod } from '@getvitops/astro';
1020
+ import vitops, { gitLastmod } from "@getvitops/astro";
746
1021
  vitops({ sitemap: { serialize: await gitLastmod() } });
747
1022
  ```
748
1023
 
@@ -842,8 +1117,8 @@ ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails
842
1117
 
843
1118
  ```js
844
1119
  vitops({
845
- analytics: { googleAnalytics: 'G-XXXXXXXXXX', plausible: 'acme.com' },
846
- consent: { policyUrl: '/legal/cookies' },
1120
+ analytics: { googleAnalytics: "G-XXXXXXXXXX", plausible: "acme.com" },
1121
+ consent: { policyUrl: "/legal/cookies" },
847
1122
  });
848
1123
  ```
849
1124
 
@@ -983,7 +1258,7 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
983
1258
 
984
1259
  ```js
985
1260
  // astro.config.mjs
986
- getvitops({ css: { input: 'company.json', format: 'css' } });
1261
+ getvitops({ css: { input: "company.json", format: "css" } });
987
1262
  ```
988
1263
 
989
1264
  ```
@@ -1082,11 +1357,11 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
1082
1357
  getvitops({
1083
1358
  fonts: [
1084
1359
  {
1085
- name: 'League Spartan',
1086
- provider: 'fontsource',
1087
- cssVariable: '--font-league-spartan',
1088
- weights: ['100 900'],
1089
- subsets: ['latin'],
1360
+ name: "League Spartan",
1361
+ provider: "fontsource",
1362
+ cssVariable: "--font-league-spartan",
1363
+ weights: ["100 900"],
1364
+ subsets: ["latin"],
1090
1365
  preload: true,
1091
1366
  },
1092
1367
  ],
@@ -1314,14 +1589,14 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
1314
1589
  // astro.config.mjs
1315
1590
  vitops({
1316
1591
  seo: {
1317
- siteName: 'Acme',
1318
- titleTemplate: '%s · Acme',
1319
- defaultDescription: 'We make the thing.',
1592
+ siteName: "Acme",
1593
+ titleTemplate: "%s · Acme",
1594
+ defaultDescription: "We make the thing.",
1320
1595
  openGraph: {
1321
- locale: 'en_CA',
1322
- image: { url: '/og.png', alt: 'Acme', width: 1200, height: 630 },
1596
+ locale: "en_CA",
1597
+ image: { url: "/og.png", alt: "Acme", width: 1200, height: 630 },
1323
1598
  },
1324
- twitter: { site: '@acme' },
1599
+ twitter: { site: "@acme" },
1325
1600
  },
1326
1601
  });
1327
1602
  ```
@@ -1365,7 +1640,7 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
1365
1640
 
1366
1641
  ```js
1367
1642
  getvitops({ sitemap: true });
1368
- getvitops({ sitemap: { filter: (page) => !page.includes('/draft/') } });
1643
+ getvitops({ sitemap: { filter: (page) => !page.includes("/draft/") } });
1369
1644
  ```
1370
1645
 
1371
1646
  `@astrojs/sitemap` is an **optional peer** — install it yourself (`pnpm add -D @astrojs/sitemap`) and
@@ -1600,15 +1875,15 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
1600
1875
  which `@getvitops/astro` re-exports wholesale. So from `astro.config.mjs`:
1601
1876
 
1602
1877
  ```js
1603
- import { generateIconInclude } from '@getvitops/astro';
1604
- import icon from 'astro-icon';
1878
+ import { generateIconInclude } from "@getvitops/astro";
1879
+ import icon from "astro-icon";
1605
1880
 
1606
1881
  integrations: [
1607
1882
  icon({
1608
1883
  include: generateIconInclude({
1609
- ui: 'fa7-solid',
1610
- brand: 'simple-icons',
1611
- semantic: ['menu', 'close', 'search', 'github'],
1884
+ ui: "fa7-solid",
1885
+ brand: "simple-icons",
1886
+ semantic: ["menu", "close", "search", "github"],
1612
1887
  }),
1613
1888
  }),
1614
1889
  ];
@@ -1642,7 +1917,7 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
1642
1917
  ```js
1643
1918
  // astro.config.mjs
1644
1919
  getvitops({
1645
- css: { input: 'design-system.json', format: 'css', out: 'src/styles' },
1920
+ css: { input: "design-system.json", format: "css", out: "src/styles" },
1646
1921
  editor: true,
1647
1922
  });
1648
1923
  ```
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { n as TRACKING_ENDPOINT, r as resolveTracking, t as TRACKING_COOKIE } fr
2
2
  import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, relative, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { isConfig, resolveConfig } from "@getvitops/generator";
5
+ import { isConfig, renderRobotsTxt, resolveConfig } from "@getvitops/generator";
6
6
  import { collectIconRefs, faviconLinks, generateFavicons, generateIconInclude, resolveIcon, scanFiles, writeFaviconManifest } from "@getvitops/utils";
7
7
  import vitopsPlugin from "@getvitops/vite";
8
8
  import { AD_PLATFORMS, AD_PROVIDER_KEYS } from "@getvitops/utils/ads";
@@ -672,14 +672,21 @@ function vitops(opts = {}) {
672
672
  }
673
673
  }
674
674
  if (siteInput) try {
675
- const key = resolveConfig(JSON.parse(readFileSync(resolve(root, siteInput), "utf8")), opts.site?.siteEnv ?? "production").site.seo?.indexing?.indexNow?.key;
675
+ const cfg = resolveConfig(JSON.parse(readFileSync(resolve(root, siteInput), "utf8")), opts.site?.siteEnv ?? "production");
676
+ const key = cfg.site.seo?.indexing?.indexNow?.key;
676
677
  if (key) {
677
678
  mkdirSync(publicDir, { recursive: true });
678
679
  writeFileSync(join(publicDir, `${key}.txt`), `${key}\n`);
679
680
  logger.info(`IndexNow key file → public/${key}.txt`);
680
681
  }
682
+ const robotsPath = join(publicDir, "robots.txt");
683
+ if (!existsSync(robotsPath)) {
684
+ mkdirSync(publicDir, { recursive: true });
685
+ writeFileSync(robotsPath, renderRobotsTxt(cfg, { env: opts.site?.siteEnv ?? "production" }));
686
+ logger.info("robots.txt → public/robots.txt");
687
+ }
681
688
  } catch (err) {
682
- logger.warn(`IndexNow key file skipped: ${err.message}`);
689
+ logger.warn(`IndexNow key file / robots.txt skipped: ${err.message}`);
683
690
  }
684
691
  let iconsData = null;
685
692
  if (opts.icons) {