@getvitops/cli 2.0.0 → 2.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 +121 -0
- package/README.md +47 -0
- package/dist/cli.mjs +355 -1
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
- package/skill/SKILL.md +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,126 @@
|
|
|
1
1
|
# @getvitops/cli
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 20e518e: Add `vitops indexing` — tell search engines about a deploy, instead of opening Search Console by hand.
|
|
8
|
+
|
|
9
|
+
Configure it in your site config under `seo.indexing`, then run it after deploying:
|
|
10
|
+
|
|
11
|
+
```jsonc
|
|
12
|
+
"seo": {
|
|
13
|
+
"indexing": {
|
|
14
|
+
"indexNow": { "key": "…" },
|
|
15
|
+
"searchConsole": { "siteUrl": "sc-domain:acme.ca" },
|
|
16
|
+
"priorityUrls": ["https://acme.ca/", "https://acme.ca/services"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
vitops indexing --dry # print the plan, make no requests
|
|
23
|
+
vitops indexing # submit
|
|
24
|
+
vitops indexing --check # a day or two later: did Google actually index them?
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It reads your sitemap, diffs it against the previous run, and submits only what changed —
|
|
28
|
+
pinging **IndexNow** and re-submitting your sitemap through the **Search Console API**.
|
|
29
|
+
`--check` inspects `priorityUrls` and exits non-zero on a page Google hasn't indexed, so a
|
|
30
|
+
scheduled CI job can catch a page that quietly fell out of the index.
|
|
31
|
+
|
|
32
|
+
**Be clear-eyed about what this can do.** Google exposes no API that requests indexing — the
|
|
33
|
+
button in Search Console isn't available anywhere, URL Inspection is read-only, and the sitemap
|
|
34
|
+
ping endpoint was removed in 2023. IndexNow reaches Bing, Yandex, Naver, Seznam and Yep; Google
|
|
35
|
+
doesn't participate. So this automates every sanctioned step and then verifies the outcome; it
|
|
36
|
+
does not make Google re-index on demand, and nothing can. Google's Indexing API is deliberately
|
|
37
|
+
not wired: it's scoped to job postings and livestreams, and using it for ordinary pages violates
|
|
38
|
+
its terms.
|
|
39
|
+
|
|
40
|
+
Also new, and worth wiring at the same time — **`gitLastmod()` for real sitemap dates**:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import vitops, { gitLastmod } from '@getvitops/astro';
|
|
44
|
+
vitops({ sitemap: { serialize: await gitLastmod() } });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`@astrojs/sitemap` emits no `<lastmod>` by default, which means a crawler is told your pages
|
|
48
|
+
exist but never that one changed — and it's what lets `vitops indexing` submit a handful of URLs instead
|
|
49
|
+
of all of them. `gitLastmod()` derives each date from the source file's last commit, and leaves a
|
|
50
|
+
page alone rather than guessing (dynamic routes, ambiguous slugs, shallow clones): an inaccurate
|
|
51
|
+
`lastmod` is worse than none, because Google stops trusting the field site-wide.
|
|
52
|
+
|
|
53
|
+
Requires `fetch-depth: 0` in CI — the default shallow clone has no history to read, and it warns
|
|
54
|
+
when that's the case.
|
|
55
|
+
|
|
56
|
+
**Credentials.** IndexNow's key is public by design (it's served at `/<key>.txt` as the ownership
|
|
57
|
+
proof), so it lives in your config, and the Astro integration writes the file into `public/` for
|
|
58
|
+
you — `vitops indexing --new-key` generates one, `--write-key <dir>` writes it for non-Astro stacks.
|
|
59
|
+
Search Console needs a service account in `VITOPS_GSC_SERVICE_ACCOUNT` or
|
|
60
|
+
`GOOGLE_APPLICATION_CREDENTIALS`, added as an owner of the property; it's never read from the
|
|
61
|
+
config file.
|
|
62
|
+
|
|
63
|
+
Persist `.vitops/` between runs (a CI cache) — that's the changed-URL state, and without it every
|
|
64
|
+
run submits everything.
|
|
65
|
+
|
|
66
|
+
An environment whose `robots` policy says `noindex` is refused outright, so pointing this at
|
|
67
|
+
staging can't publish it to a search engine.
|
|
68
|
+
|
|
69
|
+
- 9bf975a: Add `vitops media` — encode raw video into web-ready outputs, instead of hand-rolling an ffmpeg
|
|
70
|
+
script per project.
|
|
71
|
+
|
|
72
|
+
Keep unprocessed video in a `raw/` directory and run:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
vitops media --raw raw --out src/assets/processed
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Each source becomes three files: **VP9/WebM**, an **H.264/MP4** fallback, and a **JPG poster**.
|
|
79
|
+
Import them like any other asset, so your bundler content-hashes them:
|
|
80
|
+
|
|
81
|
+
```astro
|
|
82
|
+
---
|
|
83
|
+
import hero from '../assets/processed/hero.webm';
|
|
84
|
+
import poster from '../assets/processed/hero.jpg';
|
|
85
|
+
---
|
|
86
|
+
<video poster={poster.src} autoplay muted loop playsinline>
|
|
87
|
+
<source src={hero} type="video/webm" />
|
|
88
|
+
</video>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
In Astro it's an integration option — `vitops({ css, media: { raw: 'raw', out: 'src/assets/processed' } })`
|
|
92
|
+
— and it runs in the same pass that generates your CSS. `@getvitops/vite` gains a matching `media`
|
|
93
|
+
option, and `@getvitops/utils/media` exports `processMedia()` for anything else.
|
|
94
|
+
|
|
95
|
+
Defaults: capped at 1920px wide, CRF 32, audio dropped (the common case is a muted autoplay loop),
|
|
96
|
+
poster from frame 0. All of them are flags. `--dry` prints exactly what a run would do.
|
|
97
|
+
|
|
98
|
+
**Runs are cached**, on source content plus encode settings, in `.vitops/media-manifest.json` — a
|
|
99
|
+
24 MB clip that took 88 seconds the first time takes 0.14 seconds the second. A missing output
|
|
100
|
+
re-encodes; a corrupt manifest re-encodes everything. Neither ever reads as "already done", because
|
|
101
|
+
that failure is silent and no rebuild would fix it.
|
|
102
|
+
|
|
103
|
+
**Commit the outputs and the manifest.** A fresh CI clone has neither and would re-encode from
|
|
104
|
+
scratch; committing both means CI never needs ffmpeg. It also keeps your history clean — ffmpeg
|
|
105
|
+
output isn't byte-reproducible across versions, so a CI re-encode would rewrite every video on any
|
|
106
|
+
toolchain bump. Use `--force` when you mean to re-encode.
|
|
107
|
+
|
|
108
|
+
**`ffmpeg` is an external tool, not an npm dependency** — install it yourself (`brew install
|
|
109
|
+
ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails without it rather
|
|
110
|
+
than skipping: a page referencing a video that was never encoded is broken, not degraded.
|
|
111
|
+
|
|
112
|
+
Two defaults worth knowing about. The MP4 exists because older iOS and the in-app webviews inside
|
|
113
|
+
social apps still don't decode VP9, and it's written with `+faststart` — without which a browser
|
|
114
|
+
downloads the whole file before showing frame one. And a poster taken from frame 0 is often black
|
|
115
|
+
on a clip that fades in; that's `--poster-time`, not a bug.
|
|
116
|
+
|
|
117
|
+
### Patch Changes
|
|
118
|
+
|
|
119
|
+
- Updated dependencies [20e518e]
|
|
120
|
+
- Updated dependencies [9bf975a]
|
|
121
|
+
- @getvitops/generator@2.1.0
|
|
122
|
+
- @getvitops/utils@2.1.0
|
|
123
|
+
|
|
3
124
|
## 2.0.0
|
|
4
125
|
|
|
5
126
|
### 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)
|
package/dist/cli.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { dirname, join, parse, relative, resolve } from "node:path";
|
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { NUMERIC_STEPS, SCHEMA_URL, buildIconSprite, defaultConfig, enabledDocs, expandPalette, functionalRole, generate, generateDocs, generateLegal, isSiteConfig, resolveInput, resolveSiteConfig, resolveTheme, roleColorUtilities, roleHue, roleKind, validate } from "@getvitops/generator";
|
|
8
8
|
import { collectIconRefs, generateFavicons, generateIconInclude, resolveIcon, scanFiles } from "@getvitops/utils";
|
|
9
|
+
import { SNAPSHOT_PATH, collectEntries, formatPlan, getAccessToken, inspectUrl, keyFileContents, newKey, parseServiceAccount, plan, readSnapshot, resolveKeyLocation, resolveSitemapUrl, submitBatch, submitSitemap, toSnapshot, verifyKeyFile, writeSnapshot } from "@getvitops/utils/indexing";
|
|
10
|
+
import { MEDIA_MANIFEST_PATH, formatPlan as formatPlan$1, processMedia } from "@getvitops/utils/media";
|
|
9
11
|
|
|
10
12
|
//#region src/agents.ts
|
|
11
13
|
/**
|
|
@@ -530,6 +532,8 @@ function lintSource(files, v, format) {
|
|
|
530
532
|
* vitops docs [topic] [--input <json>] [--all]
|
|
531
533
|
* vitops lint [--input <json>] [--format <fmt>] [--src <dir>]
|
|
532
534
|
* vitops legal [--input <site.json>] [--doc <name>] [--format <md|html|portable-text>] [--out <dir>]
|
|
535
|
+
* vitops indexing [--input <site.json>] [--dry] [--all] [--check]
|
|
536
|
+
* vitops media [--raw <dir>] [--out <dir>] [--force] [--dry]
|
|
533
537
|
*
|
|
534
538
|
* Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).
|
|
535
539
|
* Every client brings their own consumer-editable design-system.json.
|
|
@@ -572,6 +576,8 @@ Usage:
|
|
|
572
576
|
vitops lint [options] Report framework classes in your source that resolve to nothing
|
|
573
577
|
vitops legal [options] Render legal documents from a site config
|
|
574
578
|
vitops icons [options] Report which icons your source uses, and build the sprite
|
|
579
|
+
vitops indexing [options] Tell search engines about a deploy (sitemap + IndexNow)
|
|
580
|
+
vitops media [options] Encode raw video into web-ready WebM + MP4 + poster
|
|
575
581
|
|
|
576
582
|
Generate options:
|
|
577
583
|
-i, --input <path> design-system.json or site config (default: ./design-system.json)
|
|
@@ -644,6 +650,56 @@ Legal options:
|
|
|
644
650
|
Generated from your config — a starting point, not legal advice. Review before
|
|
645
651
|
publishing, and make sure the config describes what your site actually does.
|
|
646
652
|
|
|
653
|
+
Indexing options:
|
|
654
|
+
-i, --input <path> Site config carrying seo.indexing (default: ./site.json)
|
|
655
|
+
--site-env <env> Environment to notify for (default: production). An
|
|
656
|
+
environment whose robots policy says noindex is refused.
|
|
657
|
+
--sitemap <src> Sitemap URL or local path (default: from the config)
|
|
658
|
+
--urls <list> Comma-separated URLs to submit, skipping the diff
|
|
659
|
+
--all Submit every URL in the sitemap, not just what changed
|
|
660
|
+
--dry Print the plan and exit. Makes no requests.
|
|
661
|
+
--check Read-only: ask Google whether seo.indexing.priorityUrls
|
|
662
|
+
are indexed. Exits non-zero if one is not.
|
|
663
|
+
--new-key Print a fresh IndexNow key and exit
|
|
664
|
+
--write-key <dir> Write the IndexNow key file into <dir> (for stacks with
|
|
665
|
+
no Astro integration to do it — Bricks, WordPress)
|
|
666
|
+
--snapshot <path> Changed-URL state file (default: .vitops/sitemap-snapshot.json).
|
|
667
|
+
Persist it between runs (a CI cache) or every run submits
|
|
668
|
+
everything.
|
|
669
|
+
|
|
670
|
+
What this can and cannot do: Google exposes no "request indexing" API, and its
|
|
671
|
+
sitemap ping endpoint was removed in 2023 — so it resubmits your sitemap
|
|
672
|
+
through the Search Console API and verifies the result with --check. IndexNow
|
|
673
|
+
reaches Bing, Yandex, Naver, Seznam and Yep; Google does not participate.
|
|
674
|
+
Search Console needs a service account in VITOPS_GSC_SERVICE_ACCOUNT (inline
|
|
675
|
+
JSON) or GOOGLE_APPLICATION_CREDENTIALS (a path), added as an owner of the
|
|
676
|
+
property.
|
|
677
|
+
|
|
678
|
+
Media options:
|
|
679
|
+
--raw <dir> Directory of unprocessed video, walked recursively
|
|
680
|
+
(default: ./raw)
|
|
681
|
+
-o, --out <dir> Where the encoded outputs go (default: ./src/assets/processed).
|
|
682
|
+
Subdirectories under --raw are preserved.
|
|
683
|
+
--max-width <px> Cap the output width, keeping aspect ratio (default: 1920;
|
|
684
|
+
0 disables scaling)
|
|
685
|
+
--crf <n> Quality on VP9's scale, 0-63, lower is better (default: 32).
|
|
686
|
+
The MP4 fallback uses the equivalent on H.264's scale.
|
|
687
|
+
--max-bitrate <r> Optional ceiling, e.g. 2M or 800k (default: none, which is
|
|
688
|
+
constant quality rather than constrained)
|
|
689
|
+
--audio Keep the audio track (default: dropped — the common case is
|
|
690
|
+
a muted autoplay loop)
|
|
691
|
+
--poster-time <s> Timestamp the poster frame is taken from (default: 0, which
|
|
692
|
+
is often black on a clip that fades in)
|
|
693
|
+
--outputs <list> Comma-separated: webm | mp4 | poster (default: all three)
|
|
694
|
+
--manifest <path> Cache file (default: .vitops/media-manifest.json)
|
|
695
|
+
--force Re-encode everything, ignoring the cache
|
|
696
|
+
--dry Print the plan and exit. Encodes nothing.
|
|
697
|
+
|
|
698
|
+
Needs ffmpeg on PATH — it is an external tool, not an npm dependency, and this
|
|
699
|
+
command fails rather than quietly skipping. Commit the outputs and the manifest:
|
|
700
|
+
ffmpeg output is not reproducible across versions, so re-encoding in CI churns
|
|
701
|
+
the diff on every toolchain bump. Use --force when you mean to re-encode.
|
|
702
|
+
|
|
647
703
|
Common:
|
|
648
704
|
-h, --help Show this help
|
|
649
705
|
`;
|
|
@@ -815,6 +871,99 @@ async function cmdFavicon(argv) {
|
|
|
815
871
|
fail(err.message);
|
|
816
872
|
}
|
|
817
873
|
}
|
|
874
|
+
const OUTPUT_KINDS = new Set([
|
|
875
|
+
"webm",
|
|
876
|
+
"mp4",
|
|
877
|
+
"poster"
|
|
878
|
+
]);
|
|
879
|
+
/**
|
|
880
|
+
* `vitops media` — encode a directory of raw video into web-ready outputs.
|
|
881
|
+
*
|
|
882
|
+
* Flags only, with no config file, following `favicon`: `site.favicon` exists in
|
|
883
|
+
* the schema and `cmdFavicon` still doesn't read it. The same reasoning applies
|
|
884
|
+
* more strongly here — `legal`, `icons` and `indexing` are anchored to a
|
|
885
|
+
* `SiteConfig` because what they emit *describes the site*, and an encoder setting
|
|
886
|
+
* describes a build step.
|
|
887
|
+
*/
|
|
888
|
+
async function cmdMedia(argv) {
|
|
889
|
+
const { values } = parseArgs({
|
|
890
|
+
args: argv,
|
|
891
|
+
options: {
|
|
892
|
+
raw: {
|
|
893
|
+
type: "string",
|
|
894
|
+
default: "raw"
|
|
895
|
+
},
|
|
896
|
+
out: {
|
|
897
|
+
type: "string",
|
|
898
|
+
short: "o",
|
|
899
|
+
default: "src/assets/processed"
|
|
900
|
+
},
|
|
901
|
+
"max-width": { type: "string" },
|
|
902
|
+
crf: { type: "string" },
|
|
903
|
+
"max-bitrate": { type: "string" },
|
|
904
|
+
audio: {
|
|
905
|
+
type: "boolean",
|
|
906
|
+
default: false
|
|
907
|
+
},
|
|
908
|
+
"poster-time": { type: "string" },
|
|
909
|
+
outputs: { type: "string" },
|
|
910
|
+
manifest: {
|
|
911
|
+
type: "string",
|
|
912
|
+
default: MEDIA_MANIFEST_PATH
|
|
913
|
+
},
|
|
914
|
+
force: {
|
|
915
|
+
type: "boolean",
|
|
916
|
+
default: false
|
|
917
|
+
},
|
|
918
|
+
dry: {
|
|
919
|
+
type: "boolean",
|
|
920
|
+
default: false
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
allowPositionals: false
|
|
924
|
+
});
|
|
925
|
+
const num = (flag, raw) => {
|
|
926
|
+
if (raw === void 0) return void 0;
|
|
927
|
+
const n = Number(raw);
|
|
928
|
+
if (!Number.isFinite(n) || n < 0) fail(`media: --${flag} must be a non-negative number`);
|
|
929
|
+
return n;
|
|
930
|
+
};
|
|
931
|
+
let outputs;
|
|
932
|
+
if (values.outputs) {
|
|
933
|
+
outputs = values.outputs.split(",").map((s) => s.trim());
|
|
934
|
+
for (const kind of outputs) if (!OUTPUT_KINDS.has(kind)) fail(`media: unknown output "${kind}" (expected: webm | mp4 | poster)`);
|
|
935
|
+
}
|
|
936
|
+
const maxWidth = num("max-width", values["max-width"]);
|
|
937
|
+
const crf = num("crf", values.crf);
|
|
938
|
+
const posterTime = num("poster-time", values["poster-time"]);
|
|
939
|
+
const config = {
|
|
940
|
+
...maxWidth !== void 0 ? { maxWidth } : {},
|
|
941
|
+
...crf !== void 0 ? { crf } : {},
|
|
942
|
+
...values["max-bitrate"] ? { maxBitrate: values["max-bitrate"] } : {},
|
|
943
|
+
...values.audio ? { audio: true } : {},
|
|
944
|
+
...posterTime !== void 0 ? { posterTime } : {},
|
|
945
|
+
...outputs ? { outputs } : {}
|
|
946
|
+
};
|
|
947
|
+
try {
|
|
948
|
+
const result = await processMedia({
|
|
949
|
+
raw: values.raw,
|
|
950
|
+
out: values.out,
|
|
951
|
+
config,
|
|
952
|
+
manifest: values.manifest,
|
|
953
|
+
force: values.force,
|
|
954
|
+
dry: values.dry,
|
|
955
|
+
onProgress: (message) => console.error(` … ${message}`)
|
|
956
|
+
});
|
|
957
|
+
if (values.dry) {
|
|
958
|
+
console.log(formatPlan$1(result.plan));
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
console.log(`✓ media → ${values.out} (${result.written.length} written, ${result.skipped} unchanged)`);
|
|
962
|
+
for (const note of result.plan.notes) console.error(` ! ${note}`);
|
|
963
|
+
} catch (err) {
|
|
964
|
+
fail(err.message);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
818
967
|
/**
|
|
819
968
|
* Read + parse + validate the design system at `path`, or exit with a message.
|
|
820
969
|
*
|
|
@@ -1059,6 +1208,209 @@ async function cmdLegal(argv) {
|
|
|
1059
1208
|
console.log(`✓ legal → ${outDir} (${Object.keys(files).length} files)`);
|
|
1060
1209
|
console.log(" These are generated from your config and are not legal advice — review them before publishing.");
|
|
1061
1210
|
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Adapt a `SiteConfig` to the indexing module's own option shape.
|
|
1213
|
+
*
|
|
1214
|
+
* A flat field map, as intended: `@getvitops/utils` cannot import from the
|
|
1215
|
+
* generator (the generator already depends on it), so `IndexingConfig` mirrors the
|
|
1216
|
+
* `seo.indexing` block rather than being it. The one piece of judgment here is the
|
|
1217
|
+
* origin — see below.
|
|
1218
|
+
*/
|
|
1219
|
+
function toIndexingConfig(site, siteEnv) {
|
|
1220
|
+
const indexing = site.seo?.indexing ?? {};
|
|
1221
|
+
const env = site.environments?.[siteEnv];
|
|
1222
|
+
const canonical = env?.url ?? site.domains?.canonical;
|
|
1223
|
+
return {
|
|
1224
|
+
...canonical ? { canonical } : {},
|
|
1225
|
+
...indexing.sitemapUrl ? { sitemapUrl: indexing.sitemapUrl } : {},
|
|
1226
|
+
...indexing.indexNow ? { indexNow: indexing.indexNow } : {},
|
|
1227
|
+
...indexing.searchConsole ? { searchConsole: indexing.searchConsole } : {},
|
|
1228
|
+
...indexing.priorityUrls ? { priorityUrls: indexing.priorityUrls } : {},
|
|
1229
|
+
...env?.robots ?? site.seo?.robots ? { robots: env?.robots ?? site.seo?.robots } : {}
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* The Search Console credential, from either supported source.
|
|
1234
|
+
*
|
|
1235
|
+
* Never from the config file: it is the one genuine secret in this command, and a
|
|
1236
|
+
* `site.json` is committed. Returns `undefined` rather than failing, so a site
|
|
1237
|
+
* running IndexNow alone doesn't need a GCP project at all.
|
|
1238
|
+
*/
|
|
1239
|
+
function loadServiceAccount() {
|
|
1240
|
+
const inline = process.env.VITOPS_GSC_SERVICE_ACCOUNT;
|
|
1241
|
+
const path = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
1242
|
+
const raw = inline ?? (path && existsSync(path) ? readFileSync(path, "utf8") : void 0);
|
|
1243
|
+
if (!raw) return void 0;
|
|
1244
|
+
try {
|
|
1245
|
+
return parseServiceAccount(raw);
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
fail(`${inline ? "VITOPS_GSC_SERVICE_ACCOUNT" : `GOOGLE_APPLICATION_CREDENTIALS (${path})`}: ${err.message}`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/** Read a sitemap from a URL or a local path. */
|
|
1251
|
+
const readSitemap = async (source) => {
|
|
1252
|
+
if (/^https?:\/\//i.test(source)) {
|
|
1253
|
+
const res = await fetch(source);
|
|
1254
|
+
if (!res.ok) fail(`could not fetch ${source}: ${res.status}`);
|
|
1255
|
+
return res.text();
|
|
1256
|
+
}
|
|
1257
|
+
if (!existsSync(source)) fail(`sitemap not found: ${source}`);
|
|
1258
|
+
return readFileSync(source, "utf8");
|
|
1259
|
+
};
|
|
1260
|
+
/**
|
|
1261
|
+
* Tell search engines a deploy happened.
|
|
1262
|
+
*
|
|
1263
|
+
* Worth being precise about what this is, because the obvious expectation is
|
|
1264
|
+
* wrong: **Google has no API that requests indexing.** The Search Console button
|
|
1265
|
+
* is not exposed anywhere, URL Inspection is read-only, and the sitemap ping
|
|
1266
|
+
* endpoint was removed in 2023. So the Google half of this command resubmits the
|
|
1267
|
+
* sitemap and then *verifies* the outcome with `--check`; the immediate-push half
|
|
1268
|
+
* is IndexNow, which Google does not participate in.
|
|
1269
|
+
*
|
|
1270
|
+
* The Indexing API is deliberately absent — see `@getvitops/utils/indexing`'s
|
|
1271
|
+
* `gsc.ts`.
|
|
1272
|
+
*/
|
|
1273
|
+
async function cmdIndexing(argv) {
|
|
1274
|
+
const { values } = parseArgs({
|
|
1275
|
+
args: argv,
|
|
1276
|
+
options: {
|
|
1277
|
+
input: {
|
|
1278
|
+
type: "string",
|
|
1279
|
+
short: "i",
|
|
1280
|
+
default: "site.json"
|
|
1281
|
+
},
|
|
1282
|
+
"site-env": {
|
|
1283
|
+
type: "string",
|
|
1284
|
+
default: "production"
|
|
1285
|
+
},
|
|
1286
|
+
sitemap: { type: "string" },
|
|
1287
|
+
urls: { type: "string" },
|
|
1288
|
+
all: {
|
|
1289
|
+
type: "boolean",
|
|
1290
|
+
default: false
|
|
1291
|
+
},
|
|
1292
|
+
dry: {
|
|
1293
|
+
type: "boolean",
|
|
1294
|
+
default: false
|
|
1295
|
+
},
|
|
1296
|
+
check: {
|
|
1297
|
+
type: "boolean",
|
|
1298
|
+
default: false
|
|
1299
|
+
},
|
|
1300
|
+
"new-key": {
|
|
1301
|
+
type: "boolean",
|
|
1302
|
+
default: false
|
|
1303
|
+
},
|
|
1304
|
+
"write-key": { type: "string" },
|
|
1305
|
+
snapshot: {
|
|
1306
|
+
type: "string",
|
|
1307
|
+
default: SNAPSHOT_PATH
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
allowPositionals: false
|
|
1311
|
+
});
|
|
1312
|
+
if (values["new-key"]) {
|
|
1313
|
+
const key = newKey();
|
|
1314
|
+
console.log(key);
|
|
1315
|
+
console.log(`\n Add to your site config:\n "seo": { "indexing": { "indexNow": { "key": "${key}" } } }`);
|
|
1316
|
+
console.log(` Then serve it at /${key}.txt — \`vitops indexing --write-key public\`.`);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const config = toIndexingConfig(await loadSiteConfig(resolve(values.input), values["site-env"]), values["site-env"]);
|
|
1320
|
+
if (values["write-key"] !== void 0) {
|
|
1321
|
+
const key = config.indexNow?.key;
|
|
1322
|
+
if (!key) fail("no seo.indexing.indexNow.key in the config — generate one with --new-key");
|
|
1323
|
+
const dir = resolve(values["write-key"]);
|
|
1324
|
+
mkdirSync(dir, { recursive: true });
|
|
1325
|
+
const file = join(dir, `${key}.txt`);
|
|
1326
|
+
writeFileSync(file, keyFileContents(key));
|
|
1327
|
+
console.log(`✓ IndexNow key file → ${file}`);
|
|
1328
|
+
console.log(` It must be served at ${resolveKeyLocation(config) ?? `/${key}.txt`}`);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const sitemapUrl = values.sitemap ?? resolveSitemapUrl(config);
|
|
1332
|
+
if (!sitemapUrl) fail("no sitemap to read — set seo.indexing.sitemapUrl or domains.canonical, or pass --sitemap");
|
|
1333
|
+
const explicitUrls = values.urls ? values.urls.split(",").map((u) => u.trim()).filter(Boolean) : void 0;
|
|
1334
|
+
const current = explicitUrls?.length && !values.check ? [] : (await collectEntries(sitemapUrl, readSitemap)).entries;
|
|
1335
|
+
const snapshotPath = resolve(values.snapshot);
|
|
1336
|
+
const p = plan({
|
|
1337
|
+
config: {
|
|
1338
|
+
...config,
|
|
1339
|
+
sitemapUrl
|
|
1340
|
+
},
|
|
1341
|
+
current,
|
|
1342
|
+
previous: readSnapshot(snapshotPath),
|
|
1343
|
+
explicitUrls,
|
|
1344
|
+
all: values.all
|
|
1345
|
+
});
|
|
1346
|
+
console.log(formatPlan(p));
|
|
1347
|
+
if (p.blocked) return;
|
|
1348
|
+
if (values.dry) {
|
|
1349
|
+
console.log("\n(--dry: nothing was submitted)");
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const account = loadServiceAccount();
|
|
1353
|
+
let failed = false;
|
|
1354
|
+
if (values.check) {
|
|
1355
|
+
if (!p.searchConsole.siteUrl) fail("--check needs seo.indexing.searchConsole.siteUrl (the Search Console property)");
|
|
1356
|
+
if (!account) fail("--check needs a Search Console service account in VITOPS_GSC_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS");
|
|
1357
|
+
if (p.check.length === 0) fail("--check needs seo.indexing.priorityUrls — the pages whose indexing matters");
|
|
1358
|
+
const token = await getAccessToken(account);
|
|
1359
|
+
console.log(`\nInspecting ${p.check.length} priority URL(s):`);
|
|
1360
|
+
for (const url of p.check) {
|
|
1361
|
+
const r = await inspectUrl(token, p.searchConsole.siteUrl, url);
|
|
1362
|
+
if (r.error) {
|
|
1363
|
+
console.log(` ? ${url} — ${r.error}`);
|
|
1364
|
+
failed = true;
|
|
1365
|
+
} else {
|
|
1366
|
+
console.log(` ${r.indexed ? "✓" : "✗"} ${url} — ${r.coverageState ?? r.verdict ?? "unknown"}`);
|
|
1367
|
+
if (!r.indexed) failed = true;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (failed) {
|
|
1371
|
+
console.error("\n✖ one or more priority URLs are not indexed");
|
|
1372
|
+
process.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
console.log("\n✓ every priority URL is indexed");
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
if (p.indexNow.enabled) {
|
|
1378
|
+
const { key, keyLocation } = p.indexNow;
|
|
1379
|
+
const check = await verifyKeyFile(keyLocation, key);
|
|
1380
|
+
if (!check.ok) {
|
|
1381
|
+
console.error(`✖ IndexNow key file check failed — ${check.reason}`);
|
|
1382
|
+
console.error(` Write it with: vitops indexing --write-key <public dir>`);
|
|
1383
|
+
failed = true;
|
|
1384
|
+
} else {
|
|
1385
|
+
let sent = 0;
|
|
1386
|
+
for (const batch of p.indexNow.batches) {
|
|
1387
|
+
const r = await submitBatch(p.indexNow, batch);
|
|
1388
|
+
if (!r.ok) {
|
|
1389
|
+
console.error(`✖ IndexNow: ${r.message ?? r.status}`);
|
|
1390
|
+
failed = true;
|
|
1391
|
+
break;
|
|
1392
|
+
}
|
|
1393
|
+
sent += r.urls;
|
|
1394
|
+
}
|
|
1395
|
+
if (!failed) console.log(`✓ IndexNow: ${sent} URL(s) submitted`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (p.searchConsole.enabled) if (!account) console.log("· Search Console: skipped — no VITOPS_GSC_SERVICE_ACCOUNT / GOOGLE_APPLICATION_CREDENTIALS");
|
|
1399
|
+
else {
|
|
1400
|
+
const r = await submitSitemap(await getAccessToken(account), p.searchConsole.siteUrl, sitemapUrl);
|
|
1401
|
+
if (r.ok) console.log(`✓ Search Console: sitemap resubmitted`);
|
|
1402
|
+
else {
|
|
1403
|
+
console.error(`✖ Search Console: ${r.message}`);
|
|
1404
|
+
failed = true;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (failed) process.exit(1);
|
|
1408
|
+
if (current.length > 0) {
|
|
1409
|
+
writeSnapshot(snapshotPath, toSnapshot(sitemapUrl, current, (/* @__PURE__ */ new Date()).toISOString()));
|
|
1410
|
+
console.log(` state → ${relative(process.cwd(), snapshotPath)}`);
|
|
1411
|
+
}
|
|
1412
|
+
if (p.check.length > 0) console.log(`\n Indexing is not instant. Run \`vitops indexing --check\` in a day or two to see what Google actually did.`);
|
|
1413
|
+
}
|
|
1062
1414
|
function cliVersion() {
|
|
1063
1415
|
try {
|
|
1064
1416
|
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version ?? "0.0.0";
|
|
@@ -1284,7 +1636,9 @@ async function main() {
|
|
|
1284
1636
|
case "lint": return cmdLint(rest);
|
|
1285
1637
|
case "legal": return cmdLegal(rest);
|
|
1286
1638
|
case "icons": return cmdIcons(rest);
|
|
1287
|
-
|
|
1639
|
+
case "indexing": return cmdIndexing(rest);
|
|
1640
|
+
case "media": return cmdMedia(rest);
|
|
1641
|
+
default: fail(`unknown command "${command}" (expected: generate | init | validate | favicon | agents | docs | lint | legal | icons | indexing | media). Try: vitops --help`);
|
|
1288
1642
|
}
|
|
1289
1643
|
}
|
|
1290
1644
|
main().catch((err) => fail(err.message));
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/agents.ts","../src/css-scan.ts","../src/lint-css.ts","../src/lint.ts","../src/cli.ts"],"sourcesContent":["/**\n * Side-effect-free helpers for the `vitops agents` / `vitops docs` commands.\n * Kept out of cli.ts (which runs main() on import) so they are unit-testable.\n */\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readlinkSync,\n rmSync,\n symlinkSync,\n} from 'node:fs';\nimport { dirname, join, parse, relative, resolve } from 'node:path';\n\nexport const SKILL_NAME = 'vitops-design-system';\n\n/** Marker the 0.5.0-era generated skill carried; lets us migrate that layout. */\nexport const GENERATED_MARKER = '<!-- GENERATED by @getvitops/cli';\n\n/**\n * `vitops docs` topics → docs-bundle paths (as returned by `generateDocs`).\n * Order is the `--all` concatenation order.\n */\nexport const TOPICS: Record<string, { path: string; summary: string }> = {\n classes: {\n path: 'css/classes.md',\n summary: 'CSS framework class vocabulary (colour, type, space, layout, animation, patterns)',\n },\n authoring: {\n path: 'authoring.md',\n summary: 'every design-system.json field, rendered from the JSON Schema',\n },\n formats: {\n path: 'formats.md',\n summary: \"tailwind vs css vs bricks: what's emitted vs what the platform provides\",\n },\n color: {\n path: 'concepts/color.md',\n summary: 'seeded OKLCH scales, functional tokens, automatic dark mode',\n },\n scales: {\n path: 'concepts/scales.md',\n summary: 'fluid modular type & space scales',\n },\n icons: {\n path: 'concepts/icons.md',\n summary: 'semantic icon names across sets, bundle derivation, sprite delivery',\n },\n patterns: {\n path: 'concepts/patterns.md',\n summary: 'pattern CSS: token cascade, override hooks, states, role variants',\n },\n elements: {\n path: 'bricks/elements.md',\n summary: 'Bricks Builder custom elements, controls, defaults',\n },\n};\n\n/**\n * Find the installed package's skill directory by walking up from `cwd` looking\n * for `node_modules/@getvitops/cli/skill/SKILL.md`. Returns the LOGICAL path\n * (through the `node_modules/@getvitops/cli` link, unresolved) or null.\n *\n * Deliberately not derived from import.meta.url: under pnpm the running module's\n * URL is the realpath in `node_modules/.pnpm/@getvitops+cli@<version>/…`, which\n * changes on every version bump — a symlink to it would dangle after upgrades.\n * The logical path is recreated by the package manager on every install, so a\n * link through it always resolves to the currently installed version.\n */\nexport function findSkillTarget(cwd: string): string | null {\n let dir = resolve(cwd);\n const { root } = parse(dir);\n for (;;) {\n const skillDir = join(dir, 'node_modules', '@getvitops', 'cli', 'skill');\n if (existsSync(join(skillDir, 'SKILL.md'))) return skillDir;\n if (dir === root) return null;\n dir = dirname(dir);\n }\n}\n\n/**\n * Idempotently symlink `linkPath` → `targetDir` (relative target). Replaces a\n * stale symlink; migrates a real directory left by the old emitting `vitops\n * agents` (identified by GENERATED_MARKER in its SKILL.md); refuses to clobber\n * anything else. Returns a warning string, or null on success / no-op.\n */\nexport function linkSkill(linkPath: string, targetDir: string): string | null {\n const target = relative(dirname(linkPath), resolve(targetDir));\n try {\n const st = lstatSync(linkPath, { throwIfNoEntry: false });\n if (st && !st.isSymbolicLink()) {\n const oldSkill = join(linkPath, 'SKILL.md');\n const isGenerated =\n st.isDirectory() &&\n existsSync(oldSkill) &&\n readFileSync(oldSkill, 'utf8').includes(GENERATED_MARKER);\n if (!isGenerated)\n return `left ${linkPath} alone (exists and is not a symlink) — link it to ${targetDir} yourself if wanted`;\n rmSync(linkPath, { recursive: true }); // old generated skill layout → replace\n } else if (st) {\n if (readlinkSync(linkPath) === target) return null;\n rmSync(linkPath);\n } else {\n mkdirSync(dirname(linkPath), { recursive: true });\n }\n symlinkSync(target, linkPath);\n return null;\n } catch (err) {\n return `could not symlink ${linkPath}: ${(err as Error).message}`;\n }\n}\n","/**\n * A minimal reader for hand-written CSS: rule selector, declarations as authored,\n * and the at-rule conditions the rule sits inside.\n *\n * **Why not lightningcss.** The generator owns it, and exposing an `analyzeCss`\n * from there was the obvious move — but lightningcss parses declarations into a\n * typed value model, and the typed model is exactly what these rules need to see\n * through. `display: flex` comes back as `pair`, `grid-template-columns: 1fr 1fr`\n * as `track-list`, and `margin-inline: auto` as an empty structured node. Every\n * rule here matches on *authored text*, so recovering it property-by-property\n * would mean re-implementing a serialiser for each one.\n *\n * A brace-depth scanner keeps the text intact, needs no native binary in the\n * CLI's dependency tree, and follows what `generate.ts` already does twice\n * (`stripForTailwind`, `isVariantBlock`).\n *\n * It is deliberately shallow: no specificity, no cascade, no `@supports`\n * semantics. It answers \"what did someone type in this rule\", which is all a\n * reuse suggestion needs.\n */\n\nexport interface CssDeclaration {\n property: string;\n value: string;\n}\n\nexport interface CssRule {\n selector: string;\n declarations: CssDeclaration[];\n /** Enclosing at-rule preludes, outermost first (`@media (min-width: 48rem)`). */\n conditions: string[];\n /** 1-indexed line of the selector. */\n line: number;\n}\n\n/** Strip comments, preserving newlines so line numbers survive. */\nfunction stripComments(css: string): string {\n return css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (m) => m.replace(/[^\\n]/g, ''));\n}\n\nconst lineAt = (src: string, index: number) => src.slice(0, index).split('\\n').length;\n\n/**\n * Parse declarations from a rule body. Splits on top-level `;` only, so a\n * `var(--x, a; b)` or a data URI cannot end a declaration early.\n */\nfunction parseDeclarations(body: string): CssDeclaration[] {\n const out: CssDeclaration[] = [];\n let depth = 0;\n let start = 0;\n const push = (chunk: string) => {\n const colon = chunk.indexOf(':');\n if (colon < 0) return;\n const property = chunk.slice(0, colon).trim();\n const value = chunk.slice(colon + 1).trim();\n // A nested block leaks in as a property containing `{`; skip it rather than\n // reporting a declaration that was never written.\n if (!property || property.includes('{') || !value) return;\n out.push({ property, value });\n };\n for (let i = 0; i < body.length; i++) {\n const ch = body[i];\n if (ch === '(' || ch === '[') depth++;\n else if (ch === ')' || ch === ']') depth--;\n else if (ch === ';' && depth === 0) {\n push(body.slice(start, i));\n start = i + 1;\n }\n }\n push(body.slice(start));\n return out;\n}\n\n/**\n * Scan a stylesheet into its style rules.\n *\n * Never throws: this runs over consumer source that may be a fragment, and a\n * linter that dies on one unparseable file is one nobody keeps in CI.\n */\nexport function scanCss(css: string): CssRule[] {\n const src = stripComments(css);\n const rules: CssRule[] = [];\n // Each open brace pushes its prelude; at-rule preludes become `conditions` for\n // everything nested inside, style-rule preludes become a selector.\n const stack: { prelude: string; index: number }[] = [];\n let depth = 0;\n let preludeStart = 0;\n\n for (let i = 0; i < src.length; i++) {\n const ch = src[i];\n if (ch === '\"' || ch === \"'\") {\n // Skip strings whole — a brace inside content: \"{\" would desync the depth.\n const quote = ch;\n i++;\n while (i < src.length && src[i] !== quote) {\n if (src[i] === '\\\\') i++;\n i++;\n }\n continue;\n }\n if (ch === '{') {\n const raw = src.slice(preludeStart, i);\n const prelude = raw.trim();\n // Offset of the first non-whitespace character, not of `preludeStart` —\n // that sits where the previous rule ended, so a rule's reported line would\n // be the blank line above it.\n stack.push({ prelude, index: preludeStart + raw.indexOf(prelude.slice(0, 1)) });\n depth++;\n preludeStart = i + 1;\n continue;\n }\n if (ch === '}') {\n const frame = stack.pop();\n depth--;\n if (frame && !frame.prelude.startsWith('@')) {\n // A style rule: its body is everything since it opened, minus any nested\n // blocks, which `parseDeclarations` discards.\n const bodyStart = src.indexOf('{', frame.index) + 1;\n rules.push({\n selector: frame.prelude,\n declarations: parseDeclarations(src.slice(bodyStart, i)),\n conditions: stack.filter((f) => f.prelude.startsWith('@')).map((f) => f.prelude),\n line: lineAt(src, frame.index),\n });\n }\n preludeStart = i + 1;\n continue;\n }\n }\n // Unbalanced braces (a fragment, a truncated file) leave frames on the stack;\n // returning what was parsed beats reporting nothing.\n return rules.filter((r) => r.selector && depth >= 0);\n}\n\n/** `<style>` block contents from an .astro/.vue/.svelte/.html file. */\nexport function extractStyleBlocks(src: string): { css: string; line: number }[] {\n const out: { css: string; line: number }[] = [];\n for (const m of src.matchAll(/<style\\b[^>]*>([\\s\\S]*?)<\\/style>/gi)) {\n // Offset so a finding's line number points into the original file, not into\n // the extracted block.\n const line = lineAt(src, (m.index ?? 0) + m[0].indexOf('>') + 1);\n out.push({ css: m[1] ?? '', line });\n }\n return out;\n}\n","/**\n * The inverse of `lint.ts`: hand-written CSS that re-implements a framework\n * primitive.\n *\n * `lint.ts` catches a class that resolves to nothing — you named something and it\n * isn't there. This catches the costlier direction, where the code *works*: a\n * centred container written by hand, a two-column split behind a media query, a\n * flex row. Nothing is broken, so nothing ever surfaces it, and the design system\n * quietly stops being the place those decisions live.\n *\n * Every finding here is a `suggestion`, never an `error`. These are judgement\n * calls — a rule can be right about the pattern and wrong about the intent — and\n * a reuse hint that failed CI on the day it shipped would be a worse defect than\n * the drift it reports. `--strict` is there for consumers who want the ratchet.\n *\n * Rules stay conservative on purpose: each requires the *combination* that makes\n * the intent unambiguous. `margin-inline: auto` alone is not a centred track;\n * `display: flex` alone is not a cluster.\n */\nimport type { Format } from '@getvitops/generator';\nimport { type CssRule, extractStyleBlocks, scanCss } from './css-scan.ts';\nimport type { LintFinding } from './lint.ts';\n\n/**\n * The framework's own track widths. Declared in two places today\n * (`core/css/layout.css` and the tailwind emitter), so a rule keyed to the token\n * NAME rather than to a value survives either one moving.\n */\nconst TRACK_WIDTHS = ['--width-measure', '--width-breakout', '--width-spotlight'];\n\n/**\n * Column ratios that correspond to a `split-<a>-<b>` class.\n *\n * `.split` is FLEX, not grid — `display: flex` with `flex: 1` children, and a\n * ratio class setting `--_split-a`/`--_split-b`. Equal columns are the base\n * `.split` with no ratio class, which is why `1fr 1fr` is deliberately absent:\n * suggesting `split-1-1` would name a class that does not exist, and this file\n * would then be committing the very defect `lint.ts` reports.\n */\nconst SPLITS: Record<string, string> = {\n '1fr 2fr': '1-2',\n '2fr 1fr': '2-1',\n '1fr 3fr': '1-3',\n '3fr 1fr': '3-1',\n '1fr 4fr': '1-4',\n '4fr 1fr': '4-1',\n '2fr 3fr': '2-3',\n '3fr 2fr': '3-2',\n};\n\nconst decl = (rule: CssRule, property: string) =>\n rule.declarations.find((d) => d.property === property)?.value;\n\n/** Is this rule inside a `min-width` media or container query? */\nconst inMinWidthQuery = (rule: CssRule) => rule.conditions.some((c) => /min-width\\s*:/.test(c));\n\n/** The framework breakpoint a `min-width` condition corresponds to, if any. */\nfunction breakpointOf(rule: CssRule): string | null {\n const rem = rule.conditions.map((c) => /min-width\\s*:\\s*([\\d.]+)rem/.exec(c)?.[1]).find(Boolean);\n if (!rem) return null;\n const n = Number(rem);\n // The framework's container breakpoints: sm/md/lg/xl = 30/48/64/80rem.\n return n >= 80 ? 'xl' : n >= 64 ? 'lg' : n >= 48 ? 'md' : n >= 30 ? 'sm' : null;\n}\n\n/** Spell a responsive utility the way the target format actually emits it. */\nconst responsive = (bp: string, base: string, format: Format) =>\n format === 'tailwind' ? `@${bp}:${base}` : `${bp}-${base}`;\n\ntype Verdict = Omit<LintFinding, 'file' | 'line'>;\n\n/**\n * One rule = one check. Kept as data rather than a chain of ifs so adding a\n * primitive is adding an entry, and so each carries its own explanation.\n */\nconst RULES: ((rule: CssRule, format: Format) => Verdict | null)[] = [\n // ── a centred track, written by hand ──────────────────────────────────────\n (rule) => {\n const token = TRACK_WIDTHS.find(\n (t) => decl(rule, 'max-inline-size')?.includes(t) || decl(rule, 'max-width')?.includes(t),\n );\n const centred = /\\bauto\\b/.test(decl(rule, 'margin-inline') ?? decl(rule, 'margin') ?? '');\n if (!token || !centred) return null;\n return {\n cls: `${rule.selector} { max-inline-size: var(${token}); margin-inline: auto }`,\n severity: 'suggestion',\n reason:\n '`.centered` is this pattern — a grid of named tracks, not a max-width plus auto margins',\n suggestion:\n 'use `.centered`, and widen an individual child with `breakout`, `spotlight` or ' +\n '`fullbleed` on that child rather than overriding the parent',\n };\n },\n\n // ── a two-column split behind a breakpoint ────────────────────────────────\n (rule, format) => {\n const cols = decl(rule, 'grid-template-columns');\n if (!cols || !inMinWidthQuery(rule)) return null;\n const ratio = SPLITS[cols.replace(/\\s+/g, ' ').trim()];\n const bp = ratio && breakpointOf(rule);\n if (!ratio || !bp) return null;\n return {\n cls: `${rule.selector} { grid-template-columns: ${cols} }`,\n severity: 'suggestion',\n reason: `a ${cols} split behind a min-width query is the \\`split-${ratio}\\` utility`,\n suggestion:\n `${responsive(bp, `split-${ratio}`, format)} on the container — the responsive form is ` +\n 'equal below the breakpoint and takes the ratio above it, which is what the query is doing by hand',\n };\n },\n\n // ── a flex row with centred items ─────────────────────────────────────────\n (rule, format) => {\n if (decl(rule, 'display') !== 'flex') return null;\n if (decl(rule, 'align-items') !== 'center') return null;\n if (/column/.test(decl(rule, 'flex-direction') ?? '')) return null;\n return {\n cls: `${rule.selector} { display: flex; align-items: center }`,\n severity: 'suggestion',\n reason: 'the framework has this as utilities',\n suggestion:\n format === 'tailwind'\n ? \"Tailwind's own `flex items-center` (the framework drops these names in this format)\"\n : '`flex items-center`, or `.cluster-start` if this is a row of related controls',\n };\n },\n];\n\n/**\n * Lint stylesheets and the `<style>` blocks of component files.\n *\n * `files` carries raw text, same as `lintSource` — this module stays free of the\n * filesystem so the caller owns discovery.\n */\n/**\n * The banner every generated stylesheet opens with.\n *\n * Linting the generator's own output is the worst false positive this tool can\n * produce: the emitted bundle contains the framework's real implementations of\n * these very patterns, so it reports `.split` as reinventing `.split`. The\n * `css` format writes `styles.css` into the consumer's source tree (`src/styles`\n * by default), which is squarely inside what `--src` scans.\n *\n * Matched on the banner rather than on a path, because the output directory is\n * configurable and a path rule would silently stop working when someone changes\n * it.\n */\n// Case-sensitive on purpose: the banner shouts `GENERATED`, and matching it\n// case-insensitively also swallowed an authored `/* generated content here */`.\nconst GENERATED = /^\\s*\\/\\*!?\\s*GENERATED\\b|do not edit by hand\\./;\n\nexport function lintCss(files: { path: string; text: string }[], format: Format): LintFinding[] {\n const findings: LintFinding[] = [];\n const seen = new Set<string>();\n for (const file of files) {\n if (GENERATED.test(file.text.slice(0, 400))) continue;\n // A .css file is one block starting at line 1; a component file may hold\n // several, each offset to where its <style> opens so a reported line points\n // into the original file rather than into the extracted block.\n const blocks = file.path.endsWith('.css')\n ? [{ css: file.text, line: 1 }]\n : extractStyleBlocks(file.text);\n for (const block of blocks)\n for (const rule of scanCss(block.css))\n for (const check of RULES) {\n const verdict = check(rule, format);\n if (!verdict) continue;\n const line = block.line + rule.line - 1;\n const key = `${file.path}:${line}:${verdict.reason}`;\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push({ file: file.path, line, ...verdict });\n break; // one suggestion per rule — the first match wins\n }\n }\n return findings;\n}\n","/**\n * `vitops lint` — find framework classes in consumer source that resolve to\n * nothing in the format being built.\n *\n * The problem this exists for: an unknown utility class is indistinguishable\n * from a working one. Nothing errors, nothing warns, the element just doesn't\n * get the style. Two shapes of that were found in a real consumer site:\n *\n * • `bg-navy-d` — a leftover from the named-step scale (`d`/`xd`/`xxd`)\n * deleted in favour of numeric steps. The hue is real, the step is not.\n * It appeared ~10× and looked fine only because the `.card` it sat on\n * supplied its own background.\n * • `md-flex-row` — the css/bricks spelling of a responsive variant, used in\n * a `tailwind`-format project where that class is never emitted (Tailwind\n * regenerates those on demand as `@md:`). Silently inert.\n *\n * DESIGN: this deliberately only inspects classes ANCHORED TO THE CONSUMER'S\n * OWN CONFIG — a hue name, a role name, a type role, a shadow, a pattern. It\n * never tries to enumerate \"all valid classes\", because in the tailwind format\n * that would mean knowing Tailwind's entire built-in vocabulary, and every\n * unknown would be a false positive. Anchoring means a finding is always\n * actionable: you named something from your design system, and it doesn't\n * resolve.\n */\nimport { NUMERIC_STEPS, type DesignSystem, type Format } from '@getvitops/generator';\n\n/**\n * `error` — you named something from your design system and it resolves to\n * nothing. Always actionable, and fails the command.\n *\n * `suggestion` — the code works, but the framework already has a primitive for\n * it. Advisory by design: these are judgement calls, and a new rule that failed\n * CI on first run would be a worse defect than the drift it reports. `--strict`\n * promotes them for consumers who want the ratchet.\n */\nexport type LintSeverity = 'error' | 'suggestion';\n\nexport interface LintFinding {\n file: string;\n line: number;\n /** The class token, or for a CSS rule the declaration that triggered it. */\n cls: string;\n reason: string;\n suggestion?: string;\n severity: LintSeverity;\n}\n\nconst UTIL_FAMILIES = ['bg', 'text', 'icon', 'border', 'outline', 'fill', 'stroke'];\nconst BREAKPOINTS = ['sm', 'md', 'lg', 'xl'];\n\n/**\n * The modifiers actually emitted for one family + role, read back out of the\n * generator's own class list.\n *\n * This used to be a hand-maintained array, which is the wrong shape for a\n * suggestion: the two role kinds emit different modifiers (a chromatic role has\n * `solid` and no bare form; a surface role the reverse), so a single list was\n * wrong for one of them no matter what it said.\n */\nconst emittedModifiers = (roleClasses: Set<string>, fam: string, role: string): string[] => {\n const prefix = `${fam}-${role}`;\n return [...roleClasses]\n .filter((c) => c === prefix || c.startsWith(`${prefix}-`))\n .map((c) => c.slice(prefix.length + 1))\n .filter(Boolean)\n .sort();\n};\n\n/** Everything the config makes namable, so we only judge classes we can judge. */\ninterface Vocabulary {\n roles: string[];\n hues: string[];\n steps: string[];\n typeRoles: string[];\n shadows: string[];\n patterns: string[];\n /** Every role utility the generator actually emits, e.g. `bg-surface-x-muted`. */\n roleClasses: Set<string>;\n}\n\nexport function vocabulary(ds: DesignSystem, roleClasses: Iterable<string>): Vocabulary {\n return {\n roles: Object.keys(ds.colors?.roles ?? {}),\n hues: Object.keys(ds.colors?.palette ?? {}),\n steps: [...NUMERIC_STEPS],\n typeRoles: Object.keys(ds.typography?.roles ?? {}),\n shadows: Object.keys(ds.shadows ?? {}),\n patterns: Object.keys(ds.patterns?.items ?? {}),\n roleClasses: new Set(roleClasses),\n };\n}\n\n/**\n * Strip variant prefixes (`@md:`, `hover:`, `dark:`) from a candidate.\n *\n * Format-aware, because the container-query spelling only exists in tailwind.\n * Stripping `@md:` unconditionally made the check asymmetric: `md-split-1-2` in a\n * tailwind project was caught, but `@md:split-1-2` in a css/bricks project became\n * a bare `split-1-2` that matched nothing and returned null — the same silent\n * no-op, in the other direction, unreported.\n */\nconst stripVariants = (cls: string, format: Format): string => {\n let out = cls;\n while (/^@?[a-zA-Z0-9_-]+:/.test(out)) {\n if (format !== 'tailwind' && out.startsWith('@')) break;\n out = out.slice(out.indexOf(':') + 1);\n }\n return out;\n};\n\nconst nearest = (want: string, pool: string[]): string | undefined =>\n pool.find((p) => p.startsWith(want) || want.startsWith(p));\n\n/**\n * Judge one class token. Returns null when the class is none of our business —\n * which is the common case and the whole reason this is low-noise.\n */\nexport function judge(\n raw: string,\n v: Vocabulary,\n format: Format,\n): Omit<LintFinding, 'file' | 'line'> | null {\n const cls = stripVariants(raw, format);\n\n // ── the `@<bp>:` container-query spelling, in a format that has none ───────\n // Tailwind's engine expands these; css/bricks ship pre-expanded `<bp>-` classes\n // instead, so this spelling resolves to nothing there.\n const cq = /^@(sm|md|lg|xl):(.+)$/.exec(cls);\n if (cq && format !== 'tailwind')\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`@${cq[1]}:\\` container-query variants are a tailwind-format spelling; the ${format} format emits pre-expanded classes`,\n suggestion: `${cq[1]}-${cq[2]}`,\n };\n\n // ── the `<bp>-` responsive spelling ────────────────────────────────────────\n const bp = /^(sm|md|lg|xl)-(.+)$/.exec(cls);\n if (bp && format === 'tailwind')\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${bp[1]}-\\` responsive classes are not emitted in the tailwind format`,\n suggestion: `@${bp[1]}:${bp[2]} (framework breakpoints) or ${bp[1]}:${bp[2]} (Tailwind's)`,\n };\n // In css/bricks the bare-prefix form is real; fall through and judge the base\n // against the rest of the vocabulary.\n const base = bp && format !== 'tailwind' ? (bp[2] as string) : cls;\n\n // ── <util>-<hue>-<step> ────────────────────────────────────────────────────\n const util = new RegExp(`^(${UTIL_FAMILIES.join('|')})-(.+)$`).exec(base);\n if (util) {\n const [, fam, rest] = util as unknown as [string, string, string];\n // Anything the generator actually emits is valid, full stop. Checking this\n // first also settles the ambiguity below for every well-formed class.\n if (v.roleClasses.has(base)) return null;\n\n // Role and hue names can share a prefix — `defaultConfig()` has the hue\n // `brand` and the role `brand-primary`, so matching hues first would read\n // `bg-brand-primary-x-muted` as hue `brand` + step `primary-x-muted` and\n // report a bogus bad step. Take the LONGEST match across both vocabularies.\n const prefix = (names: string[]) =>\n names\n .filter((n) => rest === n || rest.startsWith(`${n}-`))\n .sort((a, b) => b.length - a.length)[0];\n const hue = prefix(v.hues);\n const role = prefix(v.roles);\n const useRole = role && (!hue || role.length > hue.length);\n\n if (useRole) {\n const mod = rest.slice((role as string).length + 1);\n const valid = emittedModifiers(v.roleClasses, fam as string, role as string);\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${fam}-${role}${mod ? `-${mod}` : ''}\\` is not emitted for the \\`${role}\\` role`,\n suggestion: valid.length\n ? `valid modifiers: ${valid.join(', ')}`\n : `the \\`${role}\\` role emits no \\`${fam}-*\\` utilities`,\n };\n }\n if (hue) {\n const step = rest.slice(hue.length + 1);\n if (step === '')\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${fam}-${hue}\\` names a palette hue with no step`,\n suggestion: `${fam}-${hue}-500, or a role such as ${fam}-${v.roles[0] ?? 'neutral'}`,\n };\n if (!v.steps.includes(step))\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${step}\\` is not a step on the \\`${hue}\\` scale (steps are 50…950)`,\n suggestion: `${fam}-${hue}-500, or a role such as ${fam}-${v.roles[0] ?? 'neutral'} which also flips in dark mode`,\n };\n return null; // valid raw step\n }\n return null; // not anchored to this config — not ours to judge\n }\n\n // ── font-<type-role> ───────────────────────────────────────────────────────\n const font = /^font-(.+)$/.exec(base);\n if (font && v.typeRoles.length && !v.typeRoles.includes(font[1] as string)) {\n const near = nearest(font[1] as string, v.typeRoles);\n // Only complain when it looks like a type role attempt, not e.g. font-bold.\n if (near)\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${font[1]}\\` is not a typography role`,\n suggestion: `font-${near}`,\n };\n }\n\n // ── drop-shadow-<name> ─────────────────────────────────────────────────────\n const ds = /^drop-shadow-(.+)$/.exec(base);\n if (ds && v.shadows.length && !v.shadows.includes(ds[1] as string))\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${ds[1]}\\` is not a defined shadow`,\n suggestion: `drop-shadow-${v.shadows[0]}`,\n };\n\n return null;\n}\n\nconst CLASS_ATTR = /(?:class|className|:class)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|\\{`([^`]*)`\\})/g;\n\n/** Extract class tokens from a source file, with line numbers. */\nexport function extractClasses(src: string): { cls: string; line: number }[] {\n const out: { cls: string; line: number }[] = [];\n for (const m of src.matchAll(CLASS_ATTR)) {\n const value = m[1] ?? m[2] ?? m[3] ?? '';\n const line = src.slice(0, m.index).split('\\n').length;\n for (const tok of value.split(/\\s+/))\n // Skip template holes and dynamic fragments — a partial token would be\n // judged as a typo and this must not cry wolf.\n if (tok && !tok.includes('${') && !tok.includes('{') && /^[@\\w:[\\]().%/-]+$/.test(tok))\n out.push({ cls: tok, line });\n }\n return out;\n}\n\nexport function lintSource(\n files: { path: string; text: string }[],\n v: Vocabulary,\n format: Format,\n): LintFinding[] {\n const findings: LintFinding[] = [];\n const seen = new Set<string>();\n for (const f of files)\n for (const { cls, line } of extractClasses(f.text)) {\n const verdict = judge(cls, v, format);\n if (!verdict) continue;\n const key = `${f.path}:${line}:${cls}`;\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push({ file: f.path, line, ...verdict });\n }\n return findings;\n}\n","#!/usr/bin/env node\n/**\n * `vitops` — CLI for the Vitops design-system generator.\n *\n * vitops generate --input <json> --format <bricks|css|tailwind|design[,…]> --out <dir>\n * vitops init [--out design-system.json] [--force]\n * vitops validate <design-system.json|site.json>\n * vitops favicon --input <svg|png> --out <dir> [--low-res <svg|png>] [--background <hex>]\n * vitops agents [--input <json>] [--out AGENTS.md] [--docs-dir <dir>]\n * vitops docs [topic] [--input <json>] [--all]\n * vitops lint [--input <json>] [--format <fmt>] [--src <dir>]\n * vitops legal [--input <site.json>] [--doc <name>] [--format <md|html|portable-text>] [--out <dir>]\n *\n * Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).\n * Every client brings their own consumer-editable design-system.json.\n */\nimport { parseArgs } from 'node:util';\nimport { writeFileSync, existsSync, readFileSync, mkdirSync } from 'node:fs';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport {\n buildIconSprite,\n generate,\n validate,\n defaultConfig,\n generateDocs,\n generateLegal,\n enabledDocs,\n isSiteConfig,\n resolveInput,\n resolveSiteConfig,\n resolveTheme,\n roleColorUtilities,\n functionalRole,\n expandPalette,\n roleHue,\n roleKind,\n SCHEMA_URL,\n type Format,\n type StylesheetFormat,\n type DesignSystem,\n type LegalDoc,\n type LegalOutput,\n type ResolvedInput,\n type SiteConfig,\n} from '@getvitops/generator';\nimport {\n collectIconRefs,\n generateFavicons,\n generateIconInclude,\n resolveIcon,\n scanFiles,\n} from '@getvitops/utils';\nimport { findSkillTarget, linkSkill, SKILL_NAME, TOPICS } from './agents.ts';\nimport { lintCss } from './lint-css.ts';\nimport { lintSource, vocabulary } from './lint.ts';\n\nconst FORMATS = new Set<Format>(['bricks', 'css', 'tailwind', 'design']);\n// `lint` judges class names against what a build emits, and the `design` format\n// emits no CSS at all — so it is a valid target for `generate` and a category\n// error for `lint`.\nconst LINT_FORMATS = new Set<StylesheetFormat>(['bricks', 'css', 'tailwind']);\n\n// `legal` reads a *site* config, not a design-system.json, and has its own\n// output set — it renders documents, not stylesheets.\nconst LEGAL_DOCS = new Set<LegalDoc>(['privacy', 'terms', 'cookies']);\nconst LEGAL_OUTPUTS = new Set<LegalOutput>(['md', 'html', 'portable-text']);\n\nconst HELP = `vitops — generate design-system outputs from a design-system.json\n\nAnywhere a command takes --input, that file may be a design-system.json OR the\nlarger site config that embeds one (company.json / site.json). The two are told\napart by shape, and a site config also supplies the site-level facts generation\nreads — its default colour scheme, its legal documents, its icon sprite.\n\nUsage:\n vitops generate [options] Generate platform output from a config\n vitops init [options] Scaffold a starter design-system.json\n vitops validate <file> Validate a config against the schema it says it is\n vitops favicon [options] Generate a favicon set from a source image\n vitops agents [options] Link the design-system agent skill + AGENTS.md pointer\n vitops docs [topic] Print live design-system reference docs to stdout\n vitops lint [options] Report framework classes in your source that resolve to nothing\n vitops legal [options] Render legal documents from a site config\n vitops icons [options] Report which icons your source uses, and build the sprite\n\nGenerate options:\n -i, --input <path> design-system.json or site config (default: ./design-system.json)\n -f, --format <list> bricks | css | tailwind | design (comma-separated; default: bricks)\n design emits DESIGN.md only (the agent-facing brief) — pair it\n with --out . to write it beside AGENTS.md, or compose:\n --format css,design\n -o, --out <dir> Output directory (default: ./dist)\n --theme <name> Which designSystem.themes entry to build, when --input is\n a site config (default: its defaultTheme, else \"default\")\n --site <path> Site config, when it is a different file from --input.\n Emits legal/*.html into the output directory (see:\n vitops legal). Omitted with a plain design-system.json:\n no legal files.\n --site-env <env> Environment whose A/B variant applies (default: production)\n\nInit options:\n -o, --out <path> Where to write (default: ./design-system.json)\n --force Overwrite an existing file\n\nFavicon options:\n -i, --input <path> Source SVG or PNG (required)\n -o, --out <dir> Output directory (default: ./public)\n --low-res <path> Optional simplified source for the 16px icon\n --background <hex> Background under the maskable icons, which must be\n opaque (default: #ffffff)\n\nAgents options:\n -i, --input <path> design-system.json or site config (default: ./design-system.json;\n validated if present)\n --theme <name> Theme to read, when --input is a site config\n -o, --out <path> Doc file to update, idempotently (default: ./AGENTS.md)\n --docs-dir <dir> Legacy layout: write the docs bundle as files to this dir\n instead of linking the packaged skill\n\nDocs options:\n <topic> classes | authoring | formats | color | scales | patterns | elements\n (no topic: list topics with summaries)\n -i, --input <path> design-system.json or site config (default: ./design-system.json)\n --theme <name> Theme to render docs from, when --input is a site config\n --all Print every topic, concatenated\n\nIcons options:\n --site <path> Site config carrying the \"icons\" block (default: ./site.json)\n --src <dir> Source to scan for icon usage (default: ./src)\n --sprite Also build the SVG sprite\n -o, --out <dir> Where to write icons.svg with --sprite (default: ./dist)\n --json Machine-readable report on stdout\n\nLint options:\n -i, --input <path> design-system.json or site config (default: ./design-system.json)\n --theme <name> Theme to judge classes against, when --input is a site config\n -f, --format <fmt> Format you build (bricks | css | tailwind; default: bricks).\n Responsive md-* classes are real in css/bricks, inert in tailwind.\n -s, --src <dir> Directory to scan (default: ./src)\n --strict Also fail on suggestions (reuse hints), not just on\n classes that resolve to nothing\n\nLegal options:\n -i, --input <path> Site config: .json, or a .js/.ts module with a default\n export (default: ./site.json)\n -d, --doc <name> privacy | terms | cookies (repeatable; default: those\n enabled in the config)\n -f, --format <fmt> md | html | portable-text (default: md)\n html suits WordPress/Bricks and plain sites;\n portable-text suits EmDash\n -o, --out <dir> Write files here (default: print to stdout)\n --site-env <env> Environment whose A/B variant applies (default: production)\n\n Generated from your config — a starting point, not legal advice. Review before\n publishing, and make sure the config describes what your site actually does.\n\nCommon:\n -h, --help Show this help\n`;\n\nfunction fail(msg: string): never {\n console.error(`✖ ${msg}`);\n process.exit(1);\n}\n\nasync function cmdGenerate(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n format: { type: 'string', short: 'f', default: 'bricks' },\n out: { type: 'string', short: 'o', default: 'dist' },\n site: { type: 'string' },\n theme: { type: 'string' },\n 'site-env': { type: 'string' },\n },\n allowPositionals: false,\n });\n const input = resolve(values.input as string);\n if (!existsSync(input)) fail(`config not found: ${input}`);\n const site = values.site ? resolve(values.site as string) : undefined;\n if (site && !existsSync(site)) fail(`site config not found: ${site}`);\n const formats = (values.format as string)\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean);\n for (const f of formats)\n if (!FORMATS.has(f as Format))\n fail(`unknown format \"${f}\" (expected: bricks | css | tailwind | design)`);\n\n for (const format of formats as Format[]) {\n try {\n const res = await generate({\n input,\n format,\n outDir: values.out as string,\n ...(site ? { site } : {}),\n ...(values.theme ? { theme: values.theme as string } : {}),\n ...(values['site-env'] ? { siteEnv: values['site-env'] as string } : {}),\n });\n console.log(`✓ ${format} → ${res.outDir} (${res.written.length} paths)`);\n } catch (err) {\n fail((err as Error).message);\n }\n }\n}\n\nfunction cmdInit(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n out: { type: 'string', short: 'o', default: 'design-system.json' },\n force: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n const out = resolve(values.out as string);\n if (existsSync(out) && !values.force) fail(`${out} already exists (use --force to overwrite)`);\n writeFileSync(out, JSON.stringify(defaultConfig(), null, 2) + '\\n');\n console.log(`✓ wrote ${out}`);\n console.log(\n ` Edit it, then: vitops generate --input ${values.out} --format tailwind --out src/styles`,\n );\n console.log(` Editors read the \"$schema\" (${SCHEMA_URL}) for autocomplete + validation.`);\n}\n\n/**\n * Validate a config against the schema its shape says it is.\n *\n * Routing on kind rather than asking the caller matters here: pointed at a site\n * config, the design-system schema used to report a single `unrecognized_keys`\n * for `designSystem` and nothing about the file's actual contents — a wrong\n * answer that reads like a right one. A site config is validated whole, which\n * also covers the cross-field integrity JSON Schema can't express.\n */\nfunction cmdValidate(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: { input: { type: 'string', short: 'i' }, 'site-env': { type: 'string' } },\n allowPositionals: true,\n });\n const path = resolve((positionals[0] ?? values.input ?? 'design-system.json') as string);\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n\n if (isSiteConfig(raw)) {\n let site: SiteConfig;\n try {\n site = resolveSiteConfig(raw, values['site-env'] as string | undefined);\n } catch (err) {\n // Already formatted as one issue per line, with field paths.\n console.error(`✖ ${path} is invalid as a site config:`);\n console.error((err as Error).message.replace(/^Invalid site config:\\n/, ''));\n process.exit(1);\n }\n // Each theme is validated in turn: `validateSite` checks the `extends` chain\n // resolves, not that what it resolves to is a complete design system, so a\n // theme could pass there and still fail to build.\n const themes = (site.designSystem?.themes ?? {}) as Parameters<typeof resolveTheme>[0];\n let bad = false;\n for (const name of Object.keys(themes)) {\n const result = validate(resolveTheme(themes, name));\n for (const w of result.warnings) console.warn(` ! designSystem.themes.${name}: ${w}`);\n if (result.ok) continue;\n bad = true;\n console.error(`✖ ${path} — designSystem.themes.${name} is not a complete design system:`);\n for (const e of result.errors)\n console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n }\n if (bad) process.exit(1);\n const n = Object.keys(themes).length;\n console.log(`✓ ${path} is a valid site config (${n} theme${n === 1 ? '' : 's'})`);\n return;\n }\n\n const result = validate(raw);\n for (const w of result.warnings) console.warn(` ! ${w}`);\n if (result.ok) {\n console.log(`✓ ${path} is valid`);\n return;\n }\n console.error(`✖ ${path} is invalid:`);\n for (const e of result.errors) console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n}\n\nasync function cmdFavicon(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i' },\n out: { type: 'string', short: 'o', default: 'public' },\n 'low-res': { type: 'string' },\n // The maskable outputs must be opaque — the OS crops them to its own shape,\n // so transparency there becomes a black frame, not \"no background\".\n background: { type: 'string' },\n },\n allowPositionals: false,\n });\n if (!values.input) fail('favicon: --input <svg|png> is required');\n const source = resolve(values.input as string);\n if (!existsSync(source)) fail(`source not found: ${source}`);\n try {\n const written = await generateFavicons({\n source,\n outputDir: values.out as string,\n ...(values['low-res'] ? { lowResSource: resolve(values['low-res'] as string) } : {}),\n ...(values.background ? { backgroundColor: values.background as string } : {}),\n });\n console.log(`✓ favicons → ${values.out} (${written.length} files)`);\n } catch (err) {\n fail((err as Error).message);\n }\n}\n\n/**\n * Read + parse + validate the design system at `path`, or exit with a message.\n *\n * `path` may be a `design-system.json` **or** the larger site config that embeds\n * one — every command that judges source against the design system (`docs`,\n * `lint`, `agents`) accepts either, so a consumer who keeps their tokens in\n * `company.json` doesn't have to maintain a second file for the tooling's sake.\n * `resolveInput` tells them apart by shape and resolves the theme.\n */\nfunction loadConfig(path: string, theme?: string): DesignSystem {\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n let resolved: ResolvedInput;\n try {\n resolved = resolveInput(raw, theme != null ? { theme } : {});\n } catch (err) {\n // resolveSiteConfig formats one issue per line; a bad --theme is one line.\n fail(`${path}: ${(err as Error).message}`);\n }\n const result = validate(resolved.designSystem);\n if (!result.ok) {\n const where = resolved.theme != null ? ` (designSystem.themes.${resolved.theme})` : '';\n console.error(`✖ ${path}${where} is invalid:`);\n for (const e of result.errors)\n console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n }\n // Warnings go to stderr, not stdout: `vitops docs <topic>` is designed to be\n // piped, so anything here must not land in the piped document. They used to\n // be dropped entirely on this path, which meant `docs` and `agents` stayed\n // silent about collisions that `generate` and `validate` both reported.\n for (const w of result.warnings) console.warn(` ! ${w}`);\n return result.data;\n}\n\n/**\n * Read + resolve a site config, or exit with a message.\n *\n * Accepts JSON, or a JS/TS module with a default export — deliberately not YAML,\n * which would mean a parser dependency for one command. `resolveSiteConfig` is\n * documented as taking an already-parsed object precisely so the loader is the\n * consumer's choice; anyone on YAML can convert it or export from a module.\n */\nasync function loadSiteConfig(path: string, siteEnv: string): Promise<SiteConfig> {\n if (!existsSync(path)) fail(`site config not found: ${path}`);\n let raw: unknown;\n if (/\\.(json)$/.test(path)) {\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n } else {\n try {\n raw = (await import(pathToFileURL(path).href)).default;\n } catch (err) {\n fail(`could not import ${path}: ${(err as Error).message}`);\n }\n if (raw == null) fail(`${path} has no default export`);\n }\n try {\n return resolveSiteConfig(raw, siteEnv);\n } catch (err) {\n // resolveSiteConfig already formats one issue per line.\n fail((err as Error).message);\n }\n}\n\n/**\n * Render the site's legal documents.\n *\n * The universal delivery path: every consumer has this CLI regardless of stack,\n * so a WordPress theme, an Eleventy build or a hand-written HTML site all get\n * the same documents without any integration code. Prints to stdout like\n * `vitops docs`, or writes files when given `--out`.\n */\n/**\n * Report the icon vocabulary a project actually uses, and optionally build the\n * sprite from it.\n *\n * A sibling of `legal` rather than a widening of `lint`: `lint` judges classes\n * against a design-system.json, whereas icons are anchored to a SiteConfig.\n *\n * Exit codes carry the same distinction the Astro integration makes. A name the\n * config DECLARES but that doesn't resolve is a config error and fails the\n * command; a name only the SCAN found is reported and tolerated, because a bare\n * unmapped name is more often a local src/icons/*.svg than a mistake. Runtime-\n * computed names are listed with file and line so they can be declared, never\n * guessed at.\n */\nasync function cmdIcons(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n site: { type: 'string', default: 'site.json' },\n src: { type: 'string', default: 'src' },\n sprite: { type: 'boolean', default: false },\n out: { type: 'string', short: 'o', default: 'dist' },\n json: { type: 'boolean', default: false },\n 'site-env': { type: 'string', default: 'production' },\n },\n allowPositionals: false,\n });\n\n const sitePath = resolve(values.site as string);\n const icons = existsSync(sitePath)\n ? (((await loadSiteConfig(sitePath, values['site-env'] as string)).icons ?? {}) as Record<\n string,\n unknown\n >)\n : {};\n const ui = (icons.ui as string) ?? 'fa7-solid';\n const brand = (icons.brand as string) ?? 'simple-icons';\n const weight = icons.weight as string | undefined;\n const weightOpt = weight ? { weight } : {};\n\n // Declared first — this throws on an unresolvable name, by design.\n let include: Record<string, string[]> = {};\n try {\n include = generateIconInclude({ ...(icons as object) } as Parameters<\n typeof generateIconInclude\n >[0]);\n } catch (e) {\n fail((e as Error).message);\n }\n const add = (prefix: string, name: string) => {\n const list = (include[prefix] ??= []);\n if (!list.includes(name)) list.push(name);\n };\n\n const srcDir = resolve(values.src as string);\n const scanned = existsSync(srcDir) ? collectIconRefs(scanFiles(srcDir)) : null;\n const unmapped: string[] = [];\n for (const name of scanned?.names ?? []) {\n const colon = name.indexOf(':');\n if (colon > 0) {\n add(name.slice(0, colon), name.slice(colon + 1));\n continue;\n }\n try {\n const q = resolveIcon(name, ui, weightOpt);\n add(q.slice(0, q.indexOf(':')), q.slice(q.indexOf(':') + 1));\n } catch {\n unmapped.push(name);\n }\n }\n\n const dynamic = (scanned?.dynamic ?? []).map((d) => ({\n file: relative(process.cwd(), d.file),\n line: d.line,\n expr: d.expr,\n }));\n const total = Object.values(include).flat().length;\n\n let sprite: { ids: string[]; missing: string[] } | null = null;\n if (values.sprite) {\n const aliases: Record<string, string> = {};\n for (const name of (icons.semantic as string[]) ?? []) {\n try {\n aliases[`icon-${name}`] = resolveIcon(name, ui, weightOpt);\n } catch {\n /* already reported by generateIconInclude above */\n }\n }\n const built = await buildIconSprite({ include, aliases });\n const outDir = resolve(values.out as string);\n mkdirSync(outDir, { recursive: true });\n writeFileSync(join(outDir, 'icons.svg'), built.svg);\n sprite = { ids: built.ids, missing: built.missing };\n }\n\n if (values.json) {\n process.stdout.write(\n `${JSON.stringify({ ui, brand, weight: weight ?? null, include, unmapped, dynamic, sprite }, null, 2)}\\n`,\n );\n } else {\n console.log(`icon sets: ui=${ui} brand=${brand}${weight ? ` weight=${weight}` : ''}`);\n for (const [prefix, names] of Object.entries(include))\n console.log(` ${prefix}: ${names.length} — ${names.join(', ')}`);\n console.log(`✓ ${total} icon(s) across ${Object.keys(include).length} set(s)`);\n if (sprite)\n console.log(\n `✓ sprite → ${resolve(values.out as string)}/icons.svg (${sprite.ids.length} symbols)`,\n );\n for (const d of dynamic)\n console.log(` ! ${d.file}:${d.line} ${d.expr} — computed at runtime, declare it in icons`);\n if (unmapped.length)\n console.log(\n ` ! not in the '${ui}' map: ${unmapped.join(', ')} (fine for a local svg or sprite id)`,\n );\n if (sprite?.missing.length)\n console.log(` ! absent from the sprite: ${sprite.missing.join(', ')}`);\n }\n\n // Dynamic holes and unmapped bare names are warnings, not failures — same\n // tolerance `lint` shows template holes, but visible rather than silent.\n if (sprite?.missing.length) process.exitCode = 1;\n}\n\nasync function cmdLegal(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'site.json' },\n doc: { type: 'string', short: 'd', multiple: true },\n format: { type: 'string', short: 'f', default: 'md' },\n out: { type: 'string', short: 'o' },\n 'site-env': { type: 'string', default: 'production' },\n },\n allowPositionals: false,\n });\n\n const output = values.format as LegalOutput;\n if (!LEGAL_OUTPUTS.has(output))\n fail(`unknown format \"${output}\" (expected: ${[...LEGAL_OUTPUTS].join(' | ')})`);\n\n const requested = (values.doc ?? []) as string[];\n for (const d of requested)\n if (!LEGAL_DOCS.has(d as LegalDoc))\n fail(`unknown doc \"${d}\" (expected: ${[...LEGAL_DOCS].join(' | ')})`);\n\n const site = await loadSiteConfig(resolve(values.input as string), values['site-env'] as string);\n const docs = requested.length ? (requested as LegalDoc[]) : enabledDocs(site);\n if (docs.length === 0)\n fail(\n 'no legal documents are enabled — set legal.privacyPolicy.enabled (or termsOfService / cookieConsent) in your site config, or name one with --doc',\n );\n\n const files = generateLegal(site, { docs, output });\n\n if (!values.out) {\n process.stdout.write(Object.values(files).join('\\n'));\n return;\n }\n const outDir = resolve(values.out as string);\n mkdirSync(outDir, { recursive: true });\n for (const [name, content] of Object.entries(files)) writeFileSync(join(outDir, name), content);\n console.log(`✓ legal → ${outDir} (${Object.keys(files).length} files)`);\n console.log(\n ' These are generated from your config and are not legal advice — review them before publishing.',\n );\n}\n\n// This CLI's own version (dist/cli.mjs → package-root package.json).\nfunction cliVersion(): string {\n try {\n return (\n JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version ??\n '0.0.0'\n );\n } catch {\n return '0.0.0';\n }\n}\n\n// The framework asset root shipped inside @getvitops/generator (holds the Bricks\n// PHP that generateDocs reads for the element reference).\nfunction generatorAssetsDir(): string {\n const pkg = createRequire(import.meta.url).resolve('@getvitops/generator/package.json');\n return join(dirname(pkg), 'assets');\n}\n\nconst AGENTS_START = '<!-- vitops:start -->';\nconst AGENTS_END = '<!-- vitops:end -->';\n\n/** Print a live reference doc (rendered from the project's config) to stdout. */\nfunction cmdDocs(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n theme: { type: 'string' },\n all: { type: 'boolean', default: false },\n },\n allowPositionals: true,\n });\n const topic = positionals[0];\n const topicList = () =>\n Object.entries(TOPICS)\n .map(([name, t]) => ` ${name.padEnd(10)} ${t.summary}`)\n .join('\\n');\n if (!topic && !values.all) {\n console.log(\n `vitops docs <topic> — print a live design-system reference to stdout\\n\\nTopics:\\n${topicList()}\\n\\nAlso: vitops docs --all (every topic, concatenated)`,\n );\n return;\n }\n if (topic && !TOPICS[topic]) fail(`unknown topic \"${topic}\". Valid topics:\\n${topicList()}`);\n const ds = loadConfig(resolve(values.input as string), values.theme as string | undefined);\n const docs = generateDocs(ds, generatorAssetsDir());\n const paths = values.all\n ? Object.values(TOPICS).map((t) => t.path)\n : [TOPICS[topic as string]!.path];\n process.stdout.write(paths.map((p) => docs[p]).join('\\n'));\n}\n\nfunction cmdLint(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n theme: { type: 'string' },\n format: { type: 'string', short: 'f', default: 'bricks' },\n src: { type: 'string', short: 's', default: 'src' },\n // Suggestions are advisory: a reuse rule that failed CI the day it shipped\n // would be a worse defect than the drift it reports.\n strict: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n const format = values.format as StylesheetFormat;\n if (!LINT_FORMATS.has(format))\n fail(`unknown format \"${format}\" (expected: bricks | css | tailwind)`);\n const ds = loadConfig(resolve(values.input as string), values.theme as string | undefined);\n const srcDir = resolve(values.src as string);\n if (!existsSync(srcDir)) fail(`source directory not found: ${srcDir}`);\n\n // Ask the generator what it actually emits rather than re-deriving the rules\n // here — a linter that models the vocabulary separately just drifts into\n // reporting classes that do exist.\n const palette = expandPalette(ds.colors.palette);\n const roleClasses = roleColorUtilities(\n Object.entries(ds.colors.roles).map(([role, spec]) =>\n functionalRole(role, roleHue(spec), palette[roleHue(spec)]!, roleKind(spec)),\n ),\n ds.colors.utilities ?? ['bg', 'text', 'icon', 'border'],\n ).map((u) => u.cls);\n\n const files = scanFiles(srcDir);\n // `.css` is not in the default extension set — the class linter reads markup,\n // not stylesheets — so the reuse rules get their own pass. Component files are\n // scanned twice on purpose: once for their class attributes, once for any\n // `<style>` block, which is where hand-rolled layout tends to accumulate.\n const cssFiles = [...files, ...scanFiles(srcDir, { exts: ['.css'] })];\n const findings = [\n ...lintSource(files, vocabulary(ds, roleClasses), format),\n ...lintCss(cssFiles, format),\n ];\n\n if (!findings.length) {\n console.log(\n `✓ no unresolvable framework classes in ${values.src} ` +\n `(${files.length} file${files.length === 1 ? '' : 's'} scanned)`,\n );\n return;\n }\n\n const errors = findings.filter((f) => f.severity === 'error');\n const suggestions = findings.filter((f) => f.severity === 'suggestion');\n // Errors first: a class that resolves to nothing is a defect, a reuse\n // suggestion is a judgement call, and mixing them buries the former.\n for (const f of [...errors, ...suggestions]) {\n console.error(`${f.file}:${f.line} ${f.severity === 'error' ? f.cls : `(${f.cls})`}`);\n console.error(` ${f.reason}`);\n if (f.suggestion) console.error(` try: ${f.suggestion}`);\n }\n\n const count = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`;\n const parts: string[] = [];\n if (errors.length) parts.push(count(errors.length, 'unresolvable class', 'unresolvable classes'));\n if (suggestions.length) parts.push(count(suggestions.length, 'suggestion', 'suggestions'));\n const failing = errors.length > 0 || (values.strict === true && suggestions.length > 0);\n console.error(\n `\\n${failing ? '✖' : '!'} ${parts.join(', ')} in ` +\n `${files.length} file${files.length === 1 ? '' : 's'}` +\n (suggestions.length && !values.strict ? ' (suggestions do not fail; use --strict)' : ''),\n );\n if (failing) process.exit(1);\n}\n\nfunction cmdAgents(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n theme: { type: 'string' },\n out: { type: 'string', short: 'o', default: 'AGENTS.md' },\n 'docs-dir': { type: 'string' },\n },\n allowPositionals: false,\n });\n const inputRel = values.input as string;\n // The command is pure wiring: validate the config when present (fail on an\n // invalid one), but don't require it — docs are rendered live by `vitops docs`.\n let ds: DesignSystem | null = null;\n if (existsSync(resolve(inputRel)))\n ds = loadConfig(resolve(inputRel), values.theme as string | undefined);\n else console.warn(` ⚠ ${inputRel} not found — run \\`vitops init\\` to scaffold one`);\n\n // Explicit --docs-dir = legacy layout: write the docs bundle as files, no skill.\n const legacyDocsDir = values['docs-dir'] as string | undefined;\n let summary: string;\n let pointer: string[];\n\n if (legacyDocsDir) {\n if (!ds) fail(`--docs-dir needs a config to render docs from (${inputRel} not found)`);\n let docs: Record<string, string> = {};\n try {\n docs = generateDocs(ds, generatorAssetsDir());\n } catch (err) {\n fail(`could not generate docs: ${(err as Error).message}`);\n }\n for (const [rel, content] of Object.entries(docs)) {\n const p = resolve(legacyDocsDir, rel);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, content);\n }\n summary = `wrote ${Object.keys(docs).length} docs to ${legacyDocsDir}/`;\n pointer = [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Reference docs (regenerate with \\`vitops agents --docs-dir ${legacyDocsDir}\\`): \\`${legacyDocsDir}/css/classes.md\\`,`,\n `\\`${legacyDocsDir}/authoring.md\\`, \\`${legacyDocsDir}/formats.md\\`, \\`${legacyDocsDir}/bricks/elements.md\\`.`,\n ];\n } else {\n // Link the packaged skill (ships inside @getvitops/cli) into the agent\n // discovery locations; nothing is generated into the repo.\n const target = findSkillTarget(process.cwd());\n if (target) {\n for (const dir of ['.agents', '.claude']) {\n const warn = linkSkill(resolve(dir, 'skills', SKILL_NAME), target);\n if (warn) console.warn(` ⚠ ${warn}`);\n }\n summary = `linked {.agents,.claude}/skills/${SKILL_NAME} → ${target}`;\n } else {\n console.warn(\n ` ⚠ could not find node_modules/@getvitops/cli/skill — install @getvitops/cli as a devDependency, then re-run \\`vitops agents\\``,\n );\n summary = 'no skill linked';\n }\n pointer = [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Full design-system context lives in the \\`${SKILL_NAME}\\` skill (linked from`,\n '`.agents/skills/` and `.claude/skills/` into the installed `@getvitops/cli`;',\n 're-link with `vitops agents` if the links are deleted — they survive version',\n 'bumps). Reference docs print live, from this project’s config, via:',\n '',\n ...Object.entries(TOPICS).map(([name, t]) => `- \\`vitops docs ${name}\\` — ${t.summary}`),\n ];\n }\n\n const block = [\n AGENTS_START,\n '## Vitops design system',\n '',\n `Styled with the Vitops design system (\\`@getvitops/*\\`); tokens live in \\`${inputRel}\\`.`,\n 'Generate output with the CLI:',\n '',\n '- `vitops generate --format tailwind --out src/styles` — Tailwind v4 / Astro',\n '- `vitops generate --format css --out dist` — standalone CSS',\n '- `vitops generate --format bricks --out <theme>/dist` — WordPress / Bricks',\n '- `vitops generate --format design --out .` — `DESIGN.md`, the agent-facing brief',\n '- `vitops init` · `vitops validate` · `vitops favicon`',\n '',\n ...pointer,\n '',\n `<!-- regenerate: vitops agents · @getvitops/cli@${cliVersion()} -->`,\n AGENTS_END,\n ].join('\\n');\n\n const outPath = resolve(values.out as string);\n let next: string;\n let action: string;\n if (existsSync(outPath)) {\n const cur = readFileSync(outPath, 'utf8');\n const s = cur.indexOf(AGENTS_START);\n const e = cur.indexOf(AGENTS_END);\n if (s !== -1 && e !== -1 && e > s) {\n next = cur.slice(0, s) + block + cur.slice(e + AGENTS_END.length);\n action = 'updated block in';\n } else {\n next = cur.replace(/\\s*$/, '') + '\\n\\n' + block + '\\n';\n action = 'appended block to';\n }\n } else {\n mkdirSync(dirname(outPath), { recursive: true });\n next = block + '\\n';\n action = 'created';\n }\n writeFileSync(outPath, next);\n console.log(`✓ ${action} ${values.out} + ${summary}`);\n}\n\nasync function main() {\n const [command, ...rest] = process.argv.slice(2);\n if (!command || command === '-h' || command === '--help' || command === 'help') {\n console.log(HELP);\n return;\n }\n switch (command) {\n case 'generate':\n return cmdGenerate(rest);\n case 'init':\n return cmdInit(rest);\n case 'validate':\n return cmdValidate(rest);\n case 'favicon':\n return cmdFavicon(rest);\n case 'agents':\n return cmdAgents(rest);\n case 'docs':\n return cmdDocs(rest);\n case 'lint':\n return cmdLint(rest);\n case 'legal':\n return cmdLegal(rest);\n case 'icons':\n return cmdIcons(rest);\n default:\n fail(\n `unknown command \"${command}\" (expected: generate | init | validate | favicon | agents | docs | lint | legal | icons). Try: vitops --help`,\n );\n }\n}\n\nmain().catch((err) => fail((err as Error).message));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["formatMediaPlan"],"sources":["../src/agents.ts","../src/css-scan.ts","../src/lint-css.ts","../src/lint.ts","../src/cli.ts"],"sourcesContent":["/**\n * Side-effect-free helpers for the `vitops agents` / `vitops docs` commands.\n * Kept out of cli.ts (which runs main() on import) so they are unit-testable.\n */\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readlinkSync,\n rmSync,\n symlinkSync,\n} from 'node:fs';\nimport { dirname, join, parse, relative, resolve } from 'node:path';\n\nexport const SKILL_NAME = 'vitops-design-system';\n\n/** Marker the 0.5.0-era generated skill carried; lets us migrate that layout. */\nexport const GENERATED_MARKER = '<!-- GENERATED by @getvitops/cli';\n\n/**\n * `vitops docs` topics → docs-bundle paths (as returned by `generateDocs`).\n * Order is the `--all` concatenation order.\n */\nexport const TOPICS: Record<string, { path: string; summary: string }> = {\n classes: {\n path: 'css/classes.md',\n summary: 'CSS framework class vocabulary (colour, type, space, layout, animation, patterns)',\n },\n authoring: {\n path: 'authoring.md',\n summary: 'every design-system.json field, rendered from the JSON Schema',\n },\n formats: {\n path: 'formats.md',\n summary: \"tailwind vs css vs bricks: what's emitted vs what the platform provides\",\n },\n color: {\n path: 'concepts/color.md',\n summary: 'seeded OKLCH scales, functional tokens, automatic dark mode',\n },\n scales: {\n path: 'concepts/scales.md',\n summary: 'fluid modular type & space scales',\n },\n icons: {\n path: 'concepts/icons.md',\n summary: 'semantic icon names across sets, bundle derivation, sprite delivery',\n },\n patterns: {\n path: 'concepts/patterns.md',\n summary: 'pattern CSS: token cascade, override hooks, states, role variants',\n },\n elements: {\n path: 'bricks/elements.md',\n summary: 'Bricks Builder custom elements, controls, defaults',\n },\n};\n\n/**\n * Find the installed package's skill directory by walking up from `cwd` looking\n * for `node_modules/@getvitops/cli/skill/SKILL.md`. Returns the LOGICAL path\n * (through the `node_modules/@getvitops/cli` link, unresolved) or null.\n *\n * Deliberately not derived from import.meta.url: under pnpm the running module's\n * URL is the realpath in `node_modules/.pnpm/@getvitops+cli@<version>/…`, which\n * changes on every version bump — a symlink to it would dangle after upgrades.\n * The logical path is recreated by the package manager on every install, so a\n * link through it always resolves to the currently installed version.\n */\nexport function findSkillTarget(cwd: string): string | null {\n let dir = resolve(cwd);\n const { root } = parse(dir);\n for (;;) {\n const skillDir = join(dir, 'node_modules', '@getvitops', 'cli', 'skill');\n if (existsSync(join(skillDir, 'SKILL.md'))) return skillDir;\n if (dir === root) return null;\n dir = dirname(dir);\n }\n}\n\n/**\n * Idempotently symlink `linkPath` → `targetDir` (relative target). Replaces a\n * stale symlink; migrates a real directory left by the old emitting `vitops\n * agents` (identified by GENERATED_MARKER in its SKILL.md); refuses to clobber\n * anything else. Returns a warning string, or null on success / no-op.\n */\nexport function linkSkill(linkPath: string, targetDir: string): string | null {\n const target = relative(dirname(linkPath), resolve(targetDir));\n try {\n const st = lstatSync(linkPath, { throwIfNoEntry: false });\n if (st && !st.isSymbolicLink()) {\n const oldSkill = join(linkPath, 'SKILL.md');\n const isGenerated =\n st.isDirectory() &&\n existsSync(oldSkill) &&\n readFileSync(oldSkill, 'utf8').includes(GENERATED_MARKER);\n if (!isGenerated)\n return `left ${linkPath} alone (exists and is not a symlink) — link it to ${targetDir} yourself if wanted`;\n rmSync(linkPath, { recursive: true }); // old generated skill layout → replace\n } else if (st) {\n if (readlinkSync(linkPath) === target) return null;\n rmSync(linkPath);\n } else {\n mkdirSync(dirname(linkPath), { recursive: true });\n }\n symlinkSync(target, linkPath);\n return null;\n } catch (err) {\n return `could not symlink ${linkPath}: ${(err as Error).message}`;\n }\n}\n","/**\n * A minimal reader for hand-written CSS: rule selector, declarations as authored,\n * and the at-rule conditions the rule sits inside.\n *\n * **Why not lightningcss.** The generator owns it, and exposing an `analyzeCss`\n * from there was the obvious move — but lightningcss parses declarations into a\n * typed value model, and the typed model is exactly what these rules need to see\n * through. `display: flex` comes back as `pair`, `grid-template-columns: 1fr 1fr`\n * as `track-list`, and `margin-inline: auto` as an empty structured node. Every\n * rule here matches on *authored text*, so recovering it property-by-property\n * would mean re-implementing a serialiser for each one.\n *\n * A brace-depth scanner keeps the text intact, needs no native binary in the\n * CLI's dependency tree, and follows what `generate.ts` already does twice\n * (`stripForTailwind`, `isVariantBlock`).\n *\n * It is deliberately shallow: no specificity, no cascade, no `@supports`\n * semantics. It answers \"what did someone type in this rule\", which is all a\n * reuse suggestion needs.\n */\n\nexport interface CssDeclaration {\n property: string;\n value: string;\n}\n\nexport interface CssRule {\n selector: string;\n declarations: CssDeclaration[];\n /** Enclosing at-rule preludes, outermost first (`@media (min-width: 48rem)`). */\n conditions: string[];\n /** 1-indexed line of the selector. */\n line: number;\n}\n\n/** Strip comments, preserving newlines so line numbers survive. */\nfunction stripComments(css: string): string {\n return css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, (m) => m.replace(/[^\\n]/g, ''));\n}\n\nconst lineAt = (src: string, index: number) => src.slice(0, index).split('\\n').length;\n\n/**\n * Parse declarations from a rule body. Splits on top-level `;` only, so a\n * `var(--x, a; b)` or a data URI cannot end a declaration early.\n */\nfunction parseDeclarations(body: string): CssDeclaration[] {\n const out: CssDeclaration[] = [];\n let depth = 0;\n let start = 0;\n const push = (chunk: string) => {\n const colon = chunk.indexOf(':');\n if (colon < 0) return;\n const property = chunk.slice(0, colon).trim();\n const value = chunk.slice(colon + 1).trim();\n // A nested block leaks in as a property containing `{`; skip it rather than\n // reporting a declaration that was never written.\n if (!property || property.includes('{') || !value) return;\n out.push({ property, value });\n };\n for (let i = 0; i < body.length; i++) {\n const ch = body[i];\n if (ch === '(' || ch === '[') depth++;\n else if (ch === ')' || ch === ']') depth--;\n else if (ch === ';' && depth === 0) {\n push(body.slice(start, i));\n start = i + 1;\n }\n }\n push(body.slice(start));\n return out;\n}\n\n/**\n * Scan a stylesheet into its style rules.\n *\n * Never throws: this runs over consumer source that may be a fragment, and a\n * linter that dies on one unparseable file is one nobody keeps in CI.\n */\nexport function scanCss(css: string): CssRule[] {\n const src = stripComments(css);\n const rules: CssRule[] = [];\n // Each open brace pushes its prelude; at-rule preludes become `conditions` for\n // everything nested inside, style-rule preludes become a selector.\n const stack: { prelude: string; index: number }[] = [];\n let depth = 0;\n let preludeStart = 0;\n\n for (let i = 0; i < src.length; i++) {\n const ch = src[i];\n if (ch === '\"' || ch === \"'\") {\n // Skip strings whole — a brace inside content: \"{\" would desync the depth.\n const quote = ch;\n i++;\n while (i < src.length && src[i] !== quote) {\n if (src[i] === '\\\\') i++;\n i++;\n }\n continue;\n }\n if (ch === '{') {\n const raw = src.slice(preludeStart, i);\n const prelude = raw.trim();\n // Offset of the first non-whitespace character, not of `preludeStart` —\n // that sits where the previous rule ended, so a rule's reported line would\n // be the blank line above it.\n stack.push({ prelude, index: preludeStart + raw.indexOf(prelude.slice(0, 1)) });\n depth++;\n preludeStart = i + 1;\n continue;\n }\n if (ch === '}') {\n const frame = stack.pop();\n depth--;\n if (frame && !frame.prelude.startsWith('@')) {\n // A style rule: its body is everything since it opened, minus any nested\n // blocks, which `parseDeclarations` discards.\n const bodyStart = src.indexOf('{', frame.index) + 1;\n rules.push({\n selector: frame.prelude,\n declarations: parseDeclarations(src.slice(bodyStart, i)),\n conditions: stack.filter((f) => f.prelude.startsWith('@')).map((f) => f.prelude),\n line: lineAt(src, frame.index),\n });\n }\n preludeStart = i + 1;\n continue;\n }\n }\n // Unbalanced braces (a fragment, a truncated file) leave frames on the stack;\n // returning what was parsed beats reporting nothing.\n return rules.filter((r) => r.selector && depth >= 0);\n}\n\n/** `<style>` block contents from an .astro/.vue/.svelte/.html file. */\nexport function extractStyleBlocks(src: string): { css: string; line: number }[] {\n const out: { css: string; line: number }[] = [];\n for (const m of src.matchAll(/<style\\b[^>]*>([\\s\\S]*?)<\\/style>/gi)) {\n // Offset so a finding's line number points into the original file, not into\n // the extracted block.\n const line = lineAt(src, (m.index ?? 0) + m[0].indexOf('>') + 1);\n out.push({ css: m[1] ?? '', line });\n }\n return out;\n}\n","/**\n * The inverse of `lint.ts`: hand-written CSS that re-implements a framework\n * primitive.\n *\n * `lint.ts` catches a class that resolves to nothing — you named something and it\n * isn't there. This catches the costlier direction, where the code *works*: a\n * centred container written by hand, a two-column split behind a media query, a\n * flex row. Nothing is broken, so nothing ever surfaces it, and the design system\n * quietly stops being the place those decisions live.\n *\n * Every finding here is a `suggestion`, never an `error`. These are judgement\n * calls — a rule can be right about the pattern and wrong about the intent — and\n * a reuse hint that failed CI on the day it shipped would be a worse defect than\n * the drift it reports. `--strict` is there for consumers who want the ratchet.\n *\n * Rules stay conservative on purpose: each requires the *combination* that makes\n * the intent unambiguous. `margin-inline: auto` alone is not a centred track;\n * `display: flex` alone is not a cluster.\n */\nimport type { Format } from '@getvitops/generator';\nimport { type CssRule, extractStyleBlocks, scanCss } from './css-scan.ts';\nimport type { LintFinding } from './lint.ts';\n\n/**\n * The framework's own track widths. Declared in two places today\n * (`core/css/layout.css` and the tailwind emitter), so a rule keyed to the token\n * NAME rather than to a value survives either one moving.\n */\nconst TRACK_WIDTHS = ['--width-measure', '--width-breakout', '--width-spotlight'];\n\n/**\n * Column ratios that correspond to a `split-<a>-<b>` class.\n *\n * `.split` is FLEX, not grid — `display: flex` with `flex: 1` children, and a\n * ratio class setting `--_split-a`/`--_split-b`. Equal columns are the base\n * `.split` with no ratio class, which is why `1fr 1fr` is deliberately absent:\n * suggesting `split-1-1` would name a class that does not exist, and this file\n * would then be committing the very defect `lint.ts` reports.\n */\nconst SPLITS: Record<string, string> = {\n '1fr 2fr': '1-2',\n '2fr 1fr': '2-1',\n '1fr 3fr': '1-3',\n '3fr 1fr': '3-1',\n '1fr 4fr': '1-4',\n '4fr 1fr': '4-1',\n '2fr 3fr': '2-3',\n '3fr 2fr': '3-2',\n};\n\nconst decl = (rule: CssRule, property: string) =>\n rule.declarations.find((d) => d.property === property)?.value;\n\n/** Is this rule inside a `min-width` media or container query? */\nconst inMinWidthQuery = (rule: CssRule) => rule.conditions.some((c) => /min-width\\s*:/.test(c));\n\n/** The framework breakpoint a `min-width` condition corresponds to, if any. */\nfunction breakpointOf(rule: CssRule): string | null {\n const rem = rule.conditions.map((c) => /min-width\\s*:\\s*([\\d.]+)rem/.exec(c)?.[1]).find(Boolean);\n if (!rem) return null;\n const n = Number(rem);\n // The framework's container breakpoints: sm/md/lg/xl = 30/48/64/80rem.\n return n >= 80 ? 'xl' : n >= 64 ? 'lg' : n >= 48 ? 'md' : n >= 30 ? 'sm' : null;\n}\n\n/** Spell a responsive utility the way the target format actually emits it. */\nconst responsive = (bp: string, base: string, format: Format) =>\n format === 'tailwind' ? `@${bp}:${base}` : `${bp}-${base}`;\n\ntype Verdict = Omit<LintFinding, 'file' | 'line'>;\n\n/**\n * One rule = one check. Kept as data rather than a chain of ifs so adding a\n * primitive is adding an entry, and so each carries its own explanation.\n */\nconst RULES: ((rule: CssRule, format: Format) => Verdict | null)[] = [\n // ── a centred track, written by hand ──────────────────────────────────────\n (rule) => {\n const token = TRACK_WIDTHS.find(\n (t) => decl(rule, 'max-inline-size')?.includes(t) || decl(rule, 'max-width')?.includes(t),\n );\n const centred = /\\bauto\\b/.test(decl(rule, 'margin-inline') ?? decl(rule, 'margin') ?? '');\n if (!token || !centred) return null;\n return {\n cls: `${rule.selector} { max-inline-size: var(${token}); margin-inline: auto }`,\n severity: 'suggestion',\n reason:\n '`.centered` is this pattern — a grid of named tracks, not a max-width plus auto margins',\n suggestion:\n 'use `.centered`, and widen an individual child with `breakout`, `spotlight` or ' +\n '`fullbleed` on that child rather than overriding the parent',\n };\n },\n\n // ── a two-column split behind a breakpoint ────────────────────────────────\n (rule, format) => {\n const cols = decl(rule, 'grid-template-columns');\n if (!cols || !inMinWidthQuery(rule)) return null;\n const ratio = SPLITS[cols.replace(/\\s+/g, ' ').trim()];\n const bp = ratio && breakpointOf(rule);\n if (!ratio || !bp) return null;\n return {\n cls: `${rule.selector} { grid-template-columns: ${cols} }`,\n severity: 'suggestion',\n reason: `a ${cols} split behind a min-width query is the \\`split-${ratio}\\` utility`,\n suggestion:\n `${responsive(bp, `split-${ratio}`, format)} on the container — the responsive form is ` +\n 'equal below the breakpoint and takes the ratio above it, which is what the query is doing by hand',\n };\n },\n\n // ── a flex row with centred items ─────────────────────────────────────────\n (rule, format) => {\n if (decl(rule, 'display') !== 'flex') return null;\n if (decl(rule, 'align-items') !== 'center') return null;\n if (/column/.test(decl(rule, 'flex-direction') ?? '')) return null;\n return {\n cls: `${rule.selector} { display: flex; align-items: center }`,\n severity: 'suggestion',\n reason: 'the framework has this as utilities',\n suggestion:\n format === 'tailwind'\n ? \"Tailwind's own `flex items-center` (the framework drops these names in this format)\"\n : '`flex items-center`, or `.cluster-start` if this is a row of related controls',\n };\n },\n];\n\n/**\n * Lint stylesheets and the `<style>` blocks of component files.\n *\n * `files` carries raw text, same as `lintSource` — this module stays free of the\n * filesystem so the caller owns discovery.\n */\n/**\n * The banner every generated stylesheet opens with.\n *\n * Linting the generator's own output is the worst false positive this tool can\n * produce: the emitted bundle contains the framework's real implementations of\n * these very patterns, so it reports `.split` as reinventing `.split`. The\n * `css` format writes `styles.css` into the consumer's source tree (`src/styles`\n * by default), which is squarely inside what `--src` scans.\n *\n * Matched on the banner rather than on a path, because the output directory is\n * configurable and a path rule would silently stop working when someone changes\n * it.\n */\n// Case-sensitive on purpose: the banner shouts `GENERATED`, and matching it\n// case-insensitively also swallowed an authored `/* generated content here */`.\nconst GENERATED = /^\\s*\\/\\*!?\\s*GENERATED\\b|do not edit by hand\\./;\n\nexport function lintCss(files: { path: string; text: string }[], format: Format): LintFinding[] {\n const findings: LintFinding[] = [];\n const seen = new Set<string>();\n for (const file of files) {\n if (GENERATED.test(file.text.slice(0, 400))) continue;\n // A .css file is one block starting at line 1; a component file may hold\n // several, each offset to where its <style> opens so a reported line points\n // into the original file rather than into the extracted block.\n const blocks = file.path.endsWith('.css')\n ? [{ css: file.text, line: 1 }]\n : extractStyleBlocks(file.text);\n for (const block of blocks)\n for (const rule of scanCss(block.css))\n for (const check of RULES) {\n const verdict = check(rule, format);\n if (!verdict) continue;\n const line = block.line + rule.line - 1;\n const key = `${file.path}:${line}:${verdict.reason}`;\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push({ file: file.path, line, ...verdict });\n break; // one suggestion per rule — the first match wins\n }\n }\n return findings;\n}\n","/**\n * `vitops lint` — find framework classes in consumer source that resolve to\n * nothing in the format being built.\n *\n * The problem this exists for: an unknown utility class is indistinguishable\n * from a working one. Nothing errors, nothing warns, the element just doesn't\n * get the style. Two shapes of that were found in a real consumer site:\n *\n * • `bg-navy-d` — a leftover from the named-step scale (`d`/`xd`/`xxd`)\n * deleted in favour of numeric steps. The hue is real, the step is not.\n * It appeared ~10× and looked fine only because the `.card` it sat on\n * supplied its own background.\n * • `md-flex-row` — the css/bricks spelling of a responsive variant, used in\n * a `tailwind`-format project where that class is never emitted (Tailwind\n * regenerates those on demand as `@md:`). Silently inert.\n *\n * DESIGN: this deliberately only inspects classes ANCHORED TO THE CONSUMER'S\n * OWN CONFIG — a hue name, a role name, a type role, a shadow, a pattern. It\n * never tries to enumerate \"all valid classes\", because in the tailwind format\n * that would mean knowing Tailwind's entire built-in vocabulary, and every\n * unknown would be a false positive. Anchoring means a finding is always\n * actionable: you named something from your design system, and it doesn't\n * resolve.\n */\nimport { NUMERIC_STEPS, type DesignSystem, type Format } from '@getvitops/generator';\n\n/**\n * `error` — you named something from your design system and it resolves to\n * nothing. Always actionable, and fails the command.\n *\n * `suggestion` — the code works, but the framework already has a primitive for\n * it. Advisory by design: these are judgement calls, and a new rule that failed\n * CI on first run would be a worse defect than the drift it reports. `--strict`\n * promotes them for consumers who want the ratchet.\n */\nexport type LintSeverity = 'error' | 'suggestion';\n\nexport interface LintFinding {\n file: string;\n line: number;\n /** The class token, or for a CSS rule the declaration that triggered it. */\n cls: string;\n reason: string;\n suggestion?: string;\n severity: LintSeverity;\n}\n\nconst UTIL_FAMILIES = ['bg', 'text', 'icon', 'border', 'outline', 'fill', 'stroke'];\nconst BREAKPOINTS = ['sm', 'md', 'lg', 'xl'];\n\n/**\n * The modifiers actually emitted for one family + role, read back out of the\n * generator's own class list.\n *\n * This used to be a hand-maintained array, which is the wrong shape for a\n * suggestion: the two role kinds emit different modifiers (a chromatic role has\n * `solid` and no bare form; a surface role the reverse), so a single list was\n * wrong for one of them no matter what it said.\n */\nconst emittedModifiers = (roleClasses: Set<string>, fam: string, role: string): string[] => {\n const prefix = `${fam}-${role}`;\n return [...roleClasses]\n .filter((c) => c === prefix || c.startsWith(`${prefix}-`))\n .map((c) => c.slice(prefix.length + 1))\n .filter(Boolean)\n .sort();\n};\n\n/** Everything the config makes namable, so we only judge classes we can judge. */\ninterface Vocabulary {\n roles: string[];\n hues: string[];\n steps: string[];\n typeRoles: string[];\n shadows: string[];\n patterns: string[];\n /** Every role utility the generator actually emits, e.g. `bg-surface-x-muted`. */\n roleClasses: Set<string>;\n}\n\nexport function vocabulary(ds: DesignSystem, roleClasses: Iterable<string>): Vocabulary {\n return {\n roles: Object.keys(ds.colors?.roles ?? {}),\n hues: Object.keys(ds.colors?.palette ?? {}),\n steps: [...NUMERIC_STEPS],\n typeRoles: Object.keys(ds.typography?.roles ?? {}),\n shadows: Object.keys(ds.shadows ?? {}),\n patterns: Object.keys(ds.patterns?.items ?? {}),\n roleClasses: new Set(roleClasses),\n };\n}\n\n/**\n * Strip variant prefixes (`@md:`, `hover:`, `dark:`) from a candidate.\n *\n * Format-aware, because the container-query spelling only exists in tailwind.\n * Stripping `@md:` unconditionally made the check asymmetric: `md-split-1-2` in a\n * tailwind project was caught, but `@md:split-1-2` in a css/bricks project became\n * a bare `split-1-2` that matched nothing and returned null — the same silent\n * no-op, in the other direction, unreported.\n */\nconst stripVariants = (cls: string, format: Format): string => {\n let out = cls;\n while (/^@?[a-zA-Z0-9_-]+:/.test(out)) {\n if (format !== 'tailwind' && out.startsWith('@')) break;\n out = out.slice(out.indexOf(':') + 1);\n }\n return out;\n};\n\nconst nearest = (want: string, pool: string[]): string | undefined =>\n pool.find((p) => p.startsWith(want) || want.startsWith(p));\n\n/**\n * Judge one class token. Returns null when the class is none of our business —\n * which is the common case and the whole reason this is low-noise.\n */\nexport function judge(\n raw: string,\n v: Vocabulary,\n format: Format,\n): Omit<LintFinding, 'file' | 'line'> | null {\n const cls = stripVariants(raw, format);\n\n // ── the `@<bp>:` container-query spelling, in a format that has none ───────\n // Tailwind's engine expands these; css/bricks ship pre-expanded `<bp>-` classes\n // instead, so this spelling resolves to nothing there.\n const cq = /^@(sm|md|lg|xl):(.+)$/.exec(cls);\n if (cq && format !== 'tailwind')\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`@${cq[1]}:\\` container-query variants are a tailwind-format spelling; the ${format} format emits pre-expanded classes`,\n suggestion: `${cq[1]}-${cq[2]}`,\n };\n\n // ── the `<bp>-` responsive spelling ────────────────────────────────────────\n const bp = /^(sm|md|lg|xl)-(.+)$/.exec(cls);\n if (bp && format === 'tailwind')\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${bp[1]}-\\` responsive classes are not emitted in the tailwind format`,\n suggestion: `@${bp[1]}:${bp[2]} (framework breakpoints) or ${bp[1]}:${bp[2]} (Tailwind's)`,\n };\n // In css/bricks the bare-prefix form is real; fall through and judge the base\n // against the rest of the vocabulary.\n const base = bp && format !== 'tailwind' ? (bp[2] as string) : cls;\n\n // ── <util>-<hue>-<step> ────────────────────────────────────────────────────\n const util = new RegExp(`^(${UTIL_FAMILIES.join('|')})-(.+)$`).exec(base);\n if (util) {\n const [, fam, rest] = util as unknown as [string, string, string];\n // Anything the generator actually emits is valid, full stop. Checking this\n // first also settles the ambiguity below for every well-formed class.\n if (v.roleClasses.has(base)) return null;\n\n // Role and hue names can share a prefix — `defaultConfig()` has the hue\n // `brand` and the role `brand-primary`, so matching hues first would read\n // `bg-brand-primary-x-muted` as hue `brand` + step `primary-x-muted` and\n // report a bogus bad step. Take the LONGEST match across both vocabularies.\n const prefix = (names: string[]) =>\n names\n .filter((n) => rest === n || rest.startsWith(`${n}-`))\n .sort((a, b) => b.length - a.length)[0];\n const hue = prefix(v.hues);\n const role = prefix(v.roles);\n const useRole = role && (!hue || role.length > hue.length);\n\n if (useRole) {\n const mod = rest.slice((role as string).length + 1);\n const valid = emittedModifiers(v.roleClasses, fam as string, role as string);\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${fam}-${role}${mod ? `-${mod}` : ''}\\` is not emitted for the \\`${role}\\` role`,\n suggestion: valid.length\n ? `valid modifiers: ${valid.join(', ')}`\n : `the \\`${role}\\` role emits no \\`${fam}-*\\` utilities`,\n };\n }\n if (hue) {\n const step = rest.slice(hue.length + 1);\n if (step === '')\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${fam}-${hue}\\` names a palette hue with no step`,\n suggestion: `${fam}-${hue}-500, or a role such as ${fam}-${v.roles[0] ?? 'neutral'}`,\n };\n if (!v.steps.includes(step))\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${step}\\` is not a step on the \\`${hue}\\` scale (steps are 50…950)`,\n suggestion: `${fam}-${hue}-500, or a role such as ${fam}-${v.roles[0] ?? 'neutral'} which also flips in dark mode`,\n };\n return null; // valid raw step\n }\n return null; // not anchored to this config — not ours to judge\n }\n\n // ── font-<type-role> ───────────────────────────────────────────────────────\n const font = /^font-(.+)$/.exec(base);\n if (font && v.typeRoles.length && !v.typeRoles.includes(font[1] as string)) {\n const near = nearest(font[1] as string, v.typeRoles);\n // Only complain when it looks like a type role attempt, not e.g. font-bold.\n if (near)\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${font[1]}\\` is not a typography role`,\n suggestion: `font-${near}`,\n };\n }\n\n // ── drop-shadow-<name> ─────────────────────────────────────────────────────\n const ds = /^drop-shadow-(.+)$/.exec(base);\n if (ds && v.shadows.length && !v.shadows.includes(ds[1] as string))\n return {\n cls: raw,\n severity: 'error' as const,\n reason: `\\`${ds[1]}\\` is not a defined shadow`,\n suggestion: `drop-shadow-${v.shadows[0]}`,\n };\n\n return null;\n}\n\nconst CLASS_ATTR = /(?:class|className|:class)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|\\{`([^`]*)`\\})/g;\n\n/** Extract class tokens from a source file, with line numbers. */\nexport function extractClasses(src: string): { cls: string; line: number }[] {\n const out: { cls: string; line: number }[] = [];\n for (const m of src.matchAll(CLASS_ATTR)) {\n const value = m[1] ?? m[2] ?? m[3] ?? '';\n const line = src.slice(0, m.index).split('\\n').length;\n for (const tok of value.split(/\\s+/))\n // Skip template holes and dynamic fragments — a partial token would be\n // judged as a typo and this must not cry wolf.\n if (tok && !tok.includes('${') && !tok.includes('{') && /^[@\\w:[\\]().%/-]+$/.test(tok))\n out.push({ cls: tok, line });\n }\n return out;\n}\n\nexport function lintSource(\n files: { path: string; text: string }[],\n v: Vocabulary,\n format: Format,\n): LintFinding[] {\n const findings: LintFinding[] = [];\n const seen = new Set<string>();\n for (const f of files)\n for (const { cls, line } of extractClasses(f.text)) {\n const verdict = judge(cls, v, format);\n if (!verdict) continue;\n const key = `${f.path}:${line}:${cls}`;\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push({ file: f.path, line, ...verdict });\n }\n return findings;\n}\n","#!/usr/bin/env node\n/**\n * `vitops` — CLI for the Vitops design-system generator.\n *\n * vitops generate --input <json> --format <bricks|css|tailwind|design[,…]> --out <dir>\n * vitops init [--out design-system.json] [--force]\n * vitops validate <design-system.json|site.json>\n * vitops favicon --input <svg|png> --out <dir> [--low-res <svg|png>] [--background <hex>]\n * vitops agents [--input <json>] [--out AGENTS.md] [--docs-dir <dir>]\n * vitops docs [topic] [--input <json>] [--all]\n * vitops lint [--input <json>] [--format <fmt>] [--src <dir>]\n * vitops legal [--input <site.json>] [--doc <name>] [--format <md|html|portable-text>] [--out <dir>]\n * vitops indexing [--input <site.json>] [--dry] [--all] [--check]\n * vitops media [--raw <dir>] [--out <dir>] [--force] [--dry]\n *\n * Thin wrapper over @getvitops/generator (generation) and @getvitops/utils (favicons).\n * Every client brings their own consumer-editable design-system.json.\n */\nimport { parseArgs } from 'node:util';\nimport { writeFileSync, existsSync, readFileSync, mkdirSync } from 'node:fs';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport {\n buildIconSprite,\n generate,\n validate,\n defaultConfig,\n generateDocs,\n generateLegal,\n enabledDocs,\n isSiteConfig,\n resolveInput,\n resolveSiteConfig,\n resolveTheme,\n roleColorUtilities,\n functionalRole,\n expandPalette,\n roleHue,\n roleKind,\n SCHEMA_URL,\n type Format,\n type StylesheetFormat,\n type DesignSystem,\n type LegalDoc,\n type LegalOutput,\n type ResolvedInput,\n type SiteConfig,\n} from '@getvitops/generator';\nimport {\n collectIconRefs,\n generateFavicons,\n generateIconInclude,\n resolveIcon,\n scanFiles,\n} from '@getvitops/utils';\n// Its own subpath: `indexing` is the only network-touching module in utils, and the\n// other commands should not pull it in.\nimport {\n SNAPSHOT_PATH,\n collectEntries,\n formatPlan,\n getAccessToken,\n inspectUrl,\n keyFileContents,\n newKey,\n parseServiceAccount,\n plan,\n readSnapshot,\n resolveKeyLocation,\n resolveSitemapUrl,\n submitBatch,\n submitSitemap,\n toSnapshot,\n verifyKeyFile,\n writeSnapshot,\n type IndexingConfig,\n type ServiceAccount,\n} from '@getvitops/utils/indexing';\n// Its own subpath for the same reason: `media` shells out to ffmpeg, and no other\n// command should carry it. `formatPlan` is aliased — indexing exports one too.\nimport {\n MEDIA_MANIFEST_PATH,\n formatPlan as formatMediaPlan,\n processMedia,\n type MediaConfig,\n type OutputKind,\n} from '@getvitops/utils/media';\nimport { findSkillTarget, linkSkill, SKILL_NAME, TOPICS } from './agents.ts';\nimport { lintCss } from './lint-css.ts';\nimport { lintSource, vocabulary } from './lint.ts';\n\nconst FORMATS = new Set<Format>(['bricks', 'css', 'tailwind', 'design']);\n// `lint` judges class names against what a build emits, and the `design` format\n// emits no CSS at all — so it is a valid target for `generate` and a category\n// error for `lint`.\nconst LINT_FORMATS = new Set<StylesheetFormat>(['bricks', 'css', 'tailwind']);\n\n// `legal` reads a *site* config, not a design-system.json, and has its own\n// output set — it renders documents, not stylesheets.\nconst LEGAL_DOCS = new Set<LegalDoc>(['privacy', 'terms', 'cookies']);\nconst LEGAL_OUTPUTS = new Set<LegalOutput>(['md', 'html', 'portable-text']);\n\nconst HELP = `vitops — generate design-system outputs from a design-system.json\n\nAnywhere a command takes --input, that file may be a design-system.json OR the\nlarger site config that embeds one (company.json / site.json). The two are told\napart by shape, and a site config also supplies the site-level facts generation\nreads — its default colour scheme, its legal documents, its icon sprite.\n\nUsage:\n vitops generate [options] Generate platform output from a config\n vitops init [options] Scaffold a starter design-system.json\n vitops validate <file> Validate a config against the schema it says it is\n vitops favicon [options] Generate a favicon set from a source image\n vitops agents [options] Link the design-system agent skill + AGENTS.md pointer\n vitops docs [topic] Print live design-system reference docs to stdout\n vitops lint [options] Report framework classes in your source that resolve to nothing\n vitops legal [options] Render legal documents from a site config\n vitops icons [options] Report which icons your source uses, and build the sprite\n vitops indexing [options] Tell search engines about a deploy (sitemap + IndexNow)\n vitops media [options] Encode raw video into web-ready WebM + MP4 + poster\n\nGenerate options:\n -i, --input <path> design-system.json or site config (default: ./design-system.json)\n -f, --format <list> bricks | css | tailwind | design (comma-separated; default: bricks)\n design emits DESIGN.md only (the agent-facing brief) — pair it\n with --out . to write it beside AGENTS.md, or compose:\n --format css,design\n -o, --out <dir> Output directory (default: ./dist)\n --theme <name> Which designSystem.themes entry to build, when --input is\n a site config (default: its defaultTheme, else \"default\")\n --site <path> Site config, when it is a different file from --input.\n Emits legal/*.html into the output directory (see:\n vitops legal). Omitted with a plain design-system.json:\n no legal files.\n --site-env <env> Environment whose A/B variant applies (default: production)\n\nInit options:\n -o, --out <path> Where to write (default: ./design-system.json)\n --force Overwrite an existing file\n\nFavicon options:\n -i, --input <path> Source SVG or PNG (required)\n -o, --out <dir> Output directory (default: ./public)\n --low-res <path> Optional simplified source for the 16px icon\n --background <hex> Background under the maskable icons, which must be\n opaque (default: #ffffff)\n\nAgents options:\n -i, --input <path> design-system.json or site config (default: ./design-system.json;\n validated if present)\n --theme <name> Theme to read, when --input is a site config\n -o, --out <path> Doc file to update, idempotently (default: ./AGENTS.md)\n --docs-dir <dir> Legacy layout: write the docs bundle as files to this dir\n instead of linking the packaged skill\n\nDocs options:\n <topic> classes | authoring | formats | color | scales | patterns | elements\n (no topic: list topics with summaries)\n -i, --input <path> design-system.json or site config (default: ./design-system.json)\n --theme <name> Theme to render docs from, when --input is a site config\n --all Print every topic, concatenated\n\nIcons options:\n --site <path> Site config carrying the \"icons\" block (default: ./site.json)\n --src <dir> Source to scan for icon usage (default: ./src)\n --sprite Also build the SVG sprite\n -o, --out <dir> Where to write icons.svg with --sprite (default: ./dist)\n --json Machine-readable report on stdout\n\nLint options:\n -i, --input <path> design-system.json or site config (default: ./design-system.json)\n --theme <name> Theme to judge classes against, when --input is a site config\n -f, --format <fmt> Format you build (bricks | css | tailwind; default: bricks).\n Responsive md-* classes are real in css/bricks, inert in tailwind.\n -s, --src <dir> Directory to scan (default: ./src)\n --strict Also fail on suggestions (reuse hints), not just on\n classes that resolve to nothing\n\nLegal options:\n -i, --input <path> Site config: .json, or a .js/.ts module with a default\n export (default: ./site.json)\n -d, --doc <name> privacy | terms | cookies (repeatable; default: those\n enabled in the config)\n -f, --format <fmt> md | html | portable-text (default: md)\n html suits WordPress/Bricks and plain sites;\n portable-text suits EmDash\n -o, --out <dir> Write files here (default: print to stdout)\n --site-env <env> Environment whose A/B variant applies (default: production)\n\n Generated from your config — a starting point, not legal advice. Review before\n publishing, and make sure the config describes what your site actually does.\n\nIndexing options:\n -i, --input <path> Site config carrying seo.indexing (default: ./site.json)\n --site-env <env> Environment to notify for (default: production). An\n environment whose robots policy says noindex is refused.\n --sitemap <src> Sitemap URL or local path (default: from the config)\n --urls <list> Comma-separated URLs to submit, skipping the diff\n --all Submit every URL in the sitemap, not just what changed\n --dry Print the plan and exit. Makes no requests.\n --check Read-only: ask Google whether seo.indexing.priorityUrls\n are indexed. Exits non-zero if one is not.\n --new-key Print a fresh IndexNow key and exit\n --write-key <dir> Write the IndexNow key file into <dir> (for stacks with\n no Astro integration to do it — Bricks, WordPress)\n --snapshot <path> Changed-URL state file (default: .vitops/sitemap-snapshot.json).\n Persist it between runs (a CI cache) or every run submits\n everything.\n\n What this can and cannot do: Google exposes no \"request indexing\" API, and its\n sitemap ping endpoint was removed in 2023 — so it resubmits your sitemap\n through the Search Console API and verifies the result with --check. IndexNow\n reaches Bing, Yandex, Naver, Seznam and Yep; Google does not participate.\n Search Console needs a service account in VITOPS_GSC_SERVICE_ACCOUNT (inline\n JSON) or GOOGLE_APPLICATION_CREDENTIALS (a path), added as an owner of the\n property.\n\nMedia options:\n --raw <dir> Directory of unprocessed video, walked recursively\n (default: ./raw)\n -o, --out <dir> Where the encoded outputs go (default: ./src/assets/processed).\n Subdirectories under --raw are preserved.\n --max-width <px> Cap the output width, keeping aspect ratio (default: 1920;\n 0 disables scaling)\n --crf <n> Quality on VP9's scale, 0-63, lower is better (default: 32).\n The MP4 fallback uses the equivalent on H.264's scale.\n --max-bitrate <r> Optional ceiling, e.g. 2M or 800k (default: none, which is\n constant quality rather than constrained)\n --audio Keep the audio track (default: dropped — the common case is\n a muted autoplay loop)\n --poster-time <s> Timestamp the poster frame is taken from (default: 0, which\n is often black on a clip that fades in)\n --outputs <list> Comma-separated: webm | mp4 | poster (default: all three)\n --manifest <path> Cache file (default: .vitops/media-manifest.json)\n --force Re-encode everything, ignoring the cache\n --dry Print the plan and exit. Encodes nothing.\n\n Needs ffmpeg on PATH — it is an external tool, not an npm dependency, and this\n command fails rather than quietly skipping. Commit the outputs and the manifest:\n ffmpeg output is not reproducible across versions, so re-encoding in CI churns\n the diff on every toolchain bump. Use --force when you mean to re-encode.\n\nCommon:\n -h, --help Show this help\n`;\n\nfunction fail(msg: string): never {\n console.error(`✖ ${msg}`);\n process.exit(1);\n}\n\nasync function cmdGenerate(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n format: { type: 'string', short: 'f', default: 'bricks' },\n out: { type: 'string', short: 'o', default: 'dist' },\n site: { type: 'string' },\n theme: { type: 'string' },\n 'site-env': { type: 'string' },\n },\n allowPositionals: false,\n });\n const input = resolve(values.input as string);\n if (!existsSync(input)) fail(`config not found: ${input}`);\n const site = values.site ? resolve(values.site as string) : undefined;\n if (site && !existsSync(site)) fail(`site config not found: ${site}`);\n const formats = (values.format as string)\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean);\n for (const f of formats)\n if (!FORMATS.has(f as Format))\n fail(`unknown format \"${f}\" (expected: bricks | css | tailwind | design)`);\n\n for (const format of formats as Format[]) {\n try {\n const res = await generate({\n input,\n format,\n outDir: values.out as string,\n ...(site ? { site } : {}),\n ...(values.theme ? { theme: values.theme as string } : {}),\n ...(values['site-env'] ? { siteEnv: values['site-env'] as string } : {}),\n });\n console.log(`✓ ${format} → ${res.outDir} (${res.written.length} paths)`);\n } catch (err) {\n fail((err as Error).message);\n }\n }\n}\n\nfunction cmdInit(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n out: { type: 'string', short: 'o', default: 'design-system.json' },\n force: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n const out = resolve(values.out as string);\n if (existsSync(out) && !values.force) fail(`${out} already exists (use --force to overwrite)`);\n writeFileSync(out, JSON.stringify(defaultConfig(), null, 2) + '\\n');\n console.log(`✓ wrote ${out}`);\n console.log(\n ` Edit it, then: vitops generate --input ${values.out} --format tailwind --out src/styles`,\n );\n console.log(` Editors read the \"$schema\" (${SCHEMA_URL}) for autocomplete + validation.`);\n}\n\n/**\n * Validate a config against the schema its shape says it is.\n *\n * Routing on kind rather than asking the caller matters here: pointed at a site\n * config, the design-system schema used to report a single `unrecognized_keys`\n * for `designSystem` and nothing about the file's actual contents — a wrong\n * answer that reads like a right one. A site config is validated whole, which\n * also covers the cross-field integrity JSON Schema can't express.\n */\nfunction cmdValidate(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: { input: { type: 'string', short: 'i' }, 'site-env': { type: 'string' } },\n allowPositionals: true,\n });\n const path = resolve((positionals[0] ?? values.input ?? 'design-system.json') as string);\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n\n if (isSiteConfig(raw)) {\n let site: SiteConfig;\n try {\n site = resolveSiteConfig(raw, values['site-env'] as string | undefined);\n } catch (err) {\n // Already formatted as one issue per line, with field paths.\n console.error(`✖ ${path} is invalid as a site config:`);\n console.error((err as Error).message.replace(/^Invalid site config:\\n/, ''));\n process.exit(1);\n }\n // Each theme is validated in turn: `validateSite` checks the `extends` chain\n // resolves, not that what it resolves to is a complete design system, so a\n // theme could pass there and still fail to build.\n const themes = (site.designSystem?.themes ?? {}) as Parameters<typeof resolveTheme>[0];\n let bad = false;\n for (const name of Object.keys(themes)) {\n const result = validate(resolveTheme(themes, name));\n for (const w of result.warnings) console.warn(` ! designSystem.themes.${name}: ${w}`);\n if (result.ok) continue;\n bad = true;\n console.error(`✖ ${path} — designSystem.themes.${name} is not a complete design system:`);\n for (const e of result.errors)\n console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n }\n if (bad) process.exit(1);\n const n = Object.keys(themes).length;\n console.log(`✓ ${path} is a valid site config (${n} theme${n === 1 ? '' : 's'})`);\n return;\n }\n\n const result = validate(raw);\n for (const w of result.warnings) console.warn(` ! ${w}`);\n if (result.ok) {\n console.log(`✓ ${path} is valid`);\n return;\n }\n console.error(`✖ ${path} is invalid:`);\n for (const e of result.errors) console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n}\n\nasync function cmdFavicon(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i' },\n out: { type: 'string', short: 'o', default: 'public' },\n 'low-res': { type: 'string' },\n // The maskable outputs must be opaque — the OS crops them to its own shape,\n // so transparency there becomes a black frame, not \"no background\".\n background: { type: 'string' },\n },\n allowPositionals: false,\n });\n if (!values.input) fail('favicon: --input <svg|png> is required');\n const source = resolve(values.input as string);\n if (!existsSync(source)) fail(`source not found: ${source}`);\n try {\n const written = await generateFavicons({\n source,\n outputDir: values.out as string,\n ...(values['low-res'] ? { lowResSource: resolve(values['low-res'] as string) } : {}),\n ...(values.background ? { backgroundColor: values.background as string } : {}),\n });\n console.log(`✓ favicons → ${values.out} (${written.length} files)`);\n } catch (err) {\n fail((err as Error).message);\n }\n}\n\nconst OUTPUT_KINDS = new Set<OutputKind>(['webm', 'mp4', 'poster']);\n\n/**\n * `vitops media` — encode a directory of raw video into web-ready outputs.\n *\n * Flags only, with no config file, following `favicon`: `site.favicon` exists in\n * the schema and `cmdFavicon` still doesn't read it. The same reasoning applies\n * more strongly here — `legal`, `icons` and `indexing` are anchored to a\n * `SiteConfig` because what they emit *describes the site*, and an encoder setting\n * describes a build step.\n */\nasync function cmdMedia(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n raw: { type: 'string', default: 'raw' },\n out: { type: 'string', short: 'o', default: 'src/assets/processed' },\n 'max-width': { type: 'string' },\n crf: { type: 'string' },\n 'max-bitrate': { type: 'string' },\n audio: { type: 'boolean', default: false },\n 'poster-time': { type: 'string' },\n outputs: { type: 'string' },\n manifest: { type: 'string', default: MEDIA_MANIFEST_PATH },\n force: { type: 'boolean', default: false },\n dry: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n\n // Parsed here rather than passed through as strings, so a typo is caught before\n // ffmpeg spends minutes encoding at a quality nobody asked for.\n const num = (flag: string, raw: string | undefined): number | undefined => {\n if (raw === undefined) return undefined;\n const n = Number(raw);\n if (!Number.isFinite(n) || n < 0) fail(`media: --${flag} must be a non-negative number`);\n return n;\n };\n\n let outputs: OutputKind[] | undefined;\n if (values.outputs) {\n outputs = (values.outputs as string).split(',').map((s) => s.trim()) as OutputKind[];\n for (const kind of outputs)\n if (!OUTPUT_KINDS.has(kind))\n fail(`media: unknown output \"${kind}\" (expected: webm | mp4 | poster)`);\n }\n\n const maxWidth = num('max-width', values['max-width'] as string | undefined);\n const crf = num('crf', values.crf as string | undefined);\n const posterTime = num('poster-time', values['poster-time'] as string | undefined);\n\n const config: MediaConfig = {\n ...(maxWidth !== undefined ? { maxWidth } : {}),\n ...(crf !== undefined ? { crf } : {}),\n ...(values['max-bitrate'] ? { maxBitrate: values['max-bitrate'] as string } : {}),\n ...(values.audio ? { audio: true } : {}),\n ...(posterTime !== undefined ? { posterTime } : {}),\n ...(outputs ? { outputs } : {}),\n };\n\n try {\n const result = await processMedia({\n raw: values.raw as string,\n out: values.out as string,\n config,\n manifest: values.manifest as string,\n force: values.force as boolean,\n dry: values.dry as boolean,\n // An encode is minutes of silence otherwise, which reads as a hang.\n onProgress: (message) => console.error(` … ${message}`),\n });\n\n if (values.dry) {\n console.log(formatMediaPlan(result.plan));\n return;\n }\n console.log(\n `✓ media → ${values.out} (${result.written.length} written, ${result.skipped} unchanged)`,\n );\n for (const note of result.plan.notes) console.error(` ! ${note}`);\n } catch (err) {\n fail((err as Error).message);\n }\n}\n\n/**\n * Read + parse + validate the design system at `path`, or exit with a message.\n *\n * `path` may be a `design-system.json` **or** the larger site config that embeds\n * one — every command that judges source against the design system (`docs`,\n * `lint`, `agents`) accepts either, so a consumer who keeps their tokens in\n * `company.json` doesn't have to maintain a second file for the tooling's sake.\n * `resolveInput` tells them apart by shape and resolves the theme.\n */\nfunction loadConfig(path: string, theme?: string): DesignSystem {\n if (!existsSync(path)) fail(`config not found: ${path}`);\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n let resolved: ResolvedInput;\n try {\n resolved = resolveInput(raw, theme != null ? { theme } : {});\n } catch (err) {\n // resolveSiteConfig formats one issue per line; a bad --theme is one line.\n fail(`${path}: ${(err as Error).message}`);\n }\n const result = validate(resolved.designSystem);\n if (!result.ok) {\n const where = resolved.theme != null ? ` (designSystem.themes.${resolved.theme})` : '';\n console.error(`✖ ${path}${where} is invalid:`);\n for (const e of result.errors)\n console.error(` • ${e.path.join('.') || '(root)'}: ${e.message}`);\n process.exit(1);\n }\n // Warnings go to stderr, not stdout: `vitops docs <topic>` is designed to be\n // piped, so anything here must not land in the piped document. They used to\n // be dropped entirely on this path, which meant `docs` and `agents` stayed\n // silent about collisions that `generate` and `validate` both reported.\n for (const w of result.warnings) console.warn(` ! ${w}`);\n return result.data;\n}\n\n/**\n * Read + resolve a site config, or exit with a message.\n *\n * Accepts JSON, or a JS/TS module with a default export — deliberately not YAML,\n * which would mean a parser dependency for one command. `resolveSiteConfig` is\n * documented as taking an already-parsed object precisely so the loader is the\n * consumer's choice; anyone on YAML can convert it or export from a module.\n */\nasync function loadSiteConfig(path: string, siteEnv: string): Promise<SiteConfig> {\n if (!existsSync(path)) fail(`site config not found: ${path}`);\n let raw: unknown;\n if (/\\.(json)$/.test(path)) {\n try {\n raw = JSON.parse(readFileSync(path, 'utf8'));\n } catch (err) {\n fail(`could not parse JSON: ${(err as Error).message}`);\n }\n } else {\n try {\n raw = (await import(pathToFileURL(path).href)).default;\n } catch (err) {\n fail(`could not import ${path}: ${(err as Error).message}`);\n }\n if (raw == null) fail(`${path} has no default export`);\n }\n try {\n return resolveSiteConfig(raw, siteEnv);\n } catch (err) {\n // resolveSiteConfig already formats one issue per line.\n fail((err as Error).message);\n }\n}\n\n/**\n * Render the site's legal documents.\n *\n * The universal delivery path: every consumer has this CLI regardless of stack,\n * so a WordPress theme, an Eleventy build or a hand-written HTML site all get\n * the same documents without any integration code. Prints to stdout like\n * `vitops docs`, or writes files when given `--out`.\n */\n/**\n * Report the icon vocabulary a project actually uses, and optionally build the\n * sprite from it.\n *\n * A sibling of `legal` rather than a widening of `lint`: `lint` judges classes\n * against a design-system.json, whereas icons are anchored to a SiteConfig.\n *\n * Exit codes carry the same distinction the Astro integration makes. A name the\n * config DECLARES but that doesn't resolve is a config error and fails the\n * command; a name only the SCAN found is reported and tolerated, because a bare\n * unmapped name is more often a local src/icons/*.svg than a mistake. Runtime-\n * computed names are listed with file and line so they can be declared, never\n * guessed at.\n */\nasync function cmdIcons(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n site: { type: 'string', default: 'site.json' },\n src: { type: 'string', default: 'src' },\n sprite: { type: 'boolean', default: false },\n out: { type: 'string', short: 'o', default: 'dist' },\n json: { type: 'boolean', default: false },\n 'site-env': { type: 'string', default: 'production' },\n },\n allowPositionals: false,\n });\n\n const sitePath = resolve(values.site as string);\n const icons = existsSync(sitePath)\n ? (((await loadSiteConfig(sitePath, values['site-env'] as string)).icons ?? {}) as Record<\n string,\n unknown\n >)\n : {};\n const ui = (icons.ui as string) ?? 'fa7-solid';\n const brand = (icons.brand as string) ?? 'simple-icons';\n const weight = icons.weight as string | undefined;\n const weightOpt = weight ? { weight } : {};\n\n // Declared first — this throws on an unresolvable name, by design.\n let include: Record<string, string[]> = {};\n try {\n include = generateIconInclude({ ...(icons as object) } as Parameters<\n typeof generateIconInclude\n >[0]);\n } catch (e) {\n fail((e as Error).message);\n }\n const add = (prefix: string, name: string) => {\n const list = (include[prefix] ??= []);\n if (!list.includes(name)) list.push(name);\n };\n\n const srcDir = resolve(values.src as string);\n const scanned = existsSync(srcDir) ? collectIconRefs(scanFiles(srcDir)) : null;\n const unmapped: string[] = [];\n for (const name of scanned?.names ?? []) {\n const colon = name.indexOf(':');\n if (colon > 0) {\n add(name.slice(0, colon), name.slice(colon + 1));\n continue;\n }\n try {\n const q = resolveIcon(name, ui, weightOpt);\n add(q.slice(0, q.indexOf(':')), q.slice(q.indexOf(':') + 1));\n } catch {\n unmapped.push(name);\n }\n }\n\n const dynamic = (scanned?.dynamic ?? []).map((d) => ({\n file: relative(process.cwd(), d.file),\n line: d.line,\n expr: d.expr,\n }));\n const total = Object.values(include).flat().length;\n\n let sprite: { ids: string[]; missing: string[] } | null = null;\n if (values.sprite) {\n const aliases: Record<string, string> = {};\n for (const name of (icons.semantic as string[]) ?? []) {\n try {\n aliases[`icon-${name}`] = resolveIcon(name, ui, weightOpt);\n } catch {\n /* already reported by generateIconInclude above */\n }\n }\n const built = await buildIconSprite({ include, aliases });\n const outDir = resolve(values.out as string);\n mkdirSync(outDir, { recursive: true });\n writeFileSync(join(outDir, 'icons.svg'), built.svg);\n sprite = { ids: built.ids, missing: built.missing };\n }\n\n if (values.json) {\n process.stdout.write(\n `${JSON.stringify({ ui, brand, weight: weight ?? null, include, unmapped, dynamic, sprite }, null, 2)}\\n`,\n );\n } else {\n console.log(`icon sets: ui=${ui} brand=${brand}${weight ? ` weight=${weight}` : ''}`);\n for (const [prefix, names] of Object.entries(include))\n console.log(` ${prefix}: ${names.length} — ${names.join(', ')}`);\n console.log(`✓ ${total} icon(s) across ${Object.keys(include).length} set(s)`);\n if (sprite)\n console.log(\n `✓ sprite → ${resolve(values.out as string)}/icons.svg (${sprite.ids.length} symbols)`,\n );\n for (const d of dynamic)\n console.log(` ! ${d.file}:${d.line} ${d.expr} — computed at runtime, declare it in icons`);\n if (unmapped.length)\n console.log(\n ` ! not in the '${ui}' map: ${unmapped.join(', ')} (fine for a local svg or sprite id)`,\n );\n if (sprite?.missing.length)\n console.log(` ! absent from the sprite: ${sprite.missing.join(', ')}`);\n }\n\n // Dynamic holes and unmapped bare names are warnings, not failures — same\n // tolerance `lint` shows template holes, but visible rather than silent.\n if (sprite?.missing.length) process.exitCode = 1;\n}\n\nasync function cmdLegal(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'site.json' },\n doc: { type: 'string', short: 'd', multiple: true },\n format: { type: 'string', short: 'f', default: 'md' },\n out: { type: 'string', short: 'o' },\n 'site-env': { type: 'string', default: 'production' },\n },\n allowPositionals: false,\n });\n\n const output = values.format as LegalOutput;\n if (!LEGAL_OUTPUTS.has(output))\n fail(`unknown format \"${output}\" (expected: ${[...LEGAL_OUTPUTS].join(' | ')})`);\n\n const requested = (values.doc ?? []) as string[];\n for (const d of requested)\n if (!LEGAL_DOCS.has(d as LegalDoc))\n fail(`unknown doc \"${d}\" (expected: ${[...LEGAL_DOCS].join(' | ')})`);\n\n const site = await loadSiteConfig(resolve(values.input as string), values['site-env'] as string);\n const docs = requested.length ? (requested as LegalDoc[]) : enabledDocs(site);\n if (docs.length === 0)\n fail(\n 'no legal documents are enabled — set legal.privacyPolicy.enabled (or termsOfService / cookieConsent) in your site config, or name one with --doc',\n );\n\n const files = generateLegal(site, { docs, output });\n\n if (!values.out) {\n process.stdout.write(Object.values(files).join('\\n'));\n return;\n }\n const outDir = resolve(values.out as string);\n mkdirSync(outDir, { recursive: true });\n for (const [name, content] of Object.entries(files)) writeFileSync(join(outDir, name), content);\n console.log(`✓ legal → ${outDir} (${Object.keys(files).length} files)`);\n console.log(\n ' These are generated from your config and are not legal advice — review them before publishing.',\n );\n}\n\n/**\n * Adapt a `SiteConfig` to the indexing module's own option shape.\n *\n * A flat field map, as intended: `@getvitops/utils` cannot import from the\n * generator (the generator already depends on it), so `IndexingConfig` mirrors the\n * `seo.indexing` block rather than being it. The one piece of judgment here is the\n * origin — see below.\n */\nfunction toIndexingConfig(site: SiteConfig, siteEnv: string): IndexingConfig {\n const indexing = site.seo?.indexing ?? {};\n const env = site.environments?.[siteEnv];\n /*\n * The environment's own URL wins over `domains.canonical`.\n *\n * `domains.canonical` is the production origin — it is what absolute URLs and\n * SEO tags resolve against. Notifying a non-production environment while\n * deriving URLs from the canonical origin would submit *production* URLs under\n * the belief they were staging's, which the noindex gate below would then not\n * catch, because the gate reads the environment and the URLs would not.\n */\n const canonical = env?.url ?? site.domains?.canonical;\n return {\n ...(canonical ? { canonical } : {}),\n ...(indexing.sitemapUrl ? { sitemapUrl: indexing.sitemapUrl } : {}),\n ...(indexing.indexNow ? { indexNow: indexing.indexNow } : {}),\n ...(indexing.searchConsole ? { searchConsole: indexing.searchConsole } : {}),\n ...(indexing.priorityUrls ? { priorityUrls: indexing.priorityUrls } : {}),\n // Fall back to the site-wide policy so a site that states `noindex` once,\n // globally, is still protected.\n ...((env?.robots ?? site.seo?.robots) ? { robots: env?.robots ?? site.seo?.robots } : {}),\n };\n}\n\n/**\n * The Search Console credential, from either supported source.\n *\n * Never from the config file: it is the one genuine secret in this command, and a\n * `site.json` is committed. Returns `undefined` rather than failing, so a site\n * running IndexNow alone doesn't need a GCP project at all.\n */\nfunction loadServiceAccount(): ServiceAccount | undefined {\n const inline = process.env.VITOPS_GSC_SERVICE_ACCOUNT;\n const path = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n const raw = inline ?? (path && existsSync(path) ? readFileSync(path, 'utf8') : undefined);\n if (!raw) return undefined;\n try {\n return parseServiceAccount(raw);\n } catch (err) {\n fail(\n `${inline ? 'VITOPS_GSC_SERVICE_ACCOUNT' : `GOOGLE_APPLICATION_CREDENTIALS (${path})`}: ${(err as Error).message}`,\n );\n }\n}\n\n/** Read a sitemap from a URL or a local path. */\nconst readSitemap = async (source: string): Promise<string> => {\n if (/^https?:\\/\\//i.test(source)) {\n const res = await fetch(source);\n if (!res.ok) fail(`could not fetch ${source}: ${res.status}`);\n return res.text();\n }\n if (!existsSync(source)) fail(`sitemap not found: ${source}`);\n return readFileSync(source, 'utf8');\n};\n\n/**\n * Tell search engines a deploy happened.\n *\n * Worth being precise about what this is, because the obvious expectation is\n * wrong: **Google has no API that requests indexing.** The Search Console button\n * is not exposed anywhere, URL Inspection is read-only, and the sitemap ping\n * endpoint was removed in 2023. So the Google half of this command resubmits the\n * sitemap and then *verifies* the outcome with `--check`; the immediate-push half\n * is IndexNow, which Google does not participate in.\n *\n * The Indexing API is deliberately absent — see `@getvitops/utils/indexing`'s\n * `gsc.ts`.\n */\nasync function cmdIndexing(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'site.json' },\n 'site-env': { type: 'string', default: 'production' },\n sitemap: { type: 'string' },\n urls: { type: 'string' },\n all: { type: 'boolean', default: false },\n dry: { type: 'boolean', default: false },\n check: { type: 'boolean', default: false },\n 'new-key': { type: 'boolean', default: false },\n 'write-key': { type: 'string' },\n snapshot: { type: 'string', default: SNAPSHOT_PATH },\n },\n allowPositionals: false,\n });\n\n // Stands alone: generating a key is what you do *before* there is a config to\n // put it in.\n if (values['new-key']) {\n const key = newKey();\n console.log(key);\n console.log(\n `\\n Add to your site config:\\n \"seo\": { \"indexing\": { \"indexNow\": { \"key\": \"${key}\" } } }`,\n );\n console.log(` Then serve it at /${key}.txt — \\`vitops indexing --write-key public\\`.`);\n return;\n }\n\n const site = await loadSiteConfig(resolve(values.input as string), values['site-env'] as string);\n const config = toIndexingConfig(site, values['site-env'] as string);\n\n if (values['write-key'] !== undefined) {\n const key = config.indexNow?.key;\n if (!key) fail('no seo.indexing.indexNow.key in the config — generate one with --new-key');\n const dir = resolve(values['write-key']);\n mkdirSync(dir, { recursive: true });\n const file = join(dir, `${key}.txt`);\n writeFileSync(file, keyFileContents(key));\n console.log(`✓ IndexNow key file → ${file}`);\n console.log(` It must be served at ${resolveKeyLocation(config) ?? `/${key}.txt`}`);\n return;\n }\n\n const sitemapUrl = (values.sitemap as string | undefined) ?? resolveSitemapUrl(config);\n if (!sitemapUrl)\n fail(\n 'no sitemap to read — set seo.indexing.sitemapUrl or domains.canonical, or pass --sitemap',\n );\n\n const explicitUrls = values.urls\n ? (values.urls as string)\n .split(',')\n .map((u) => u.trim())\n .filter(Boolean)\n : undefined;\n\n // The sitemap is only needed when the URL set comes from it.\n const current =\n explicitUrls?.length && !values.check\n ? []\n : (await collectEntries(sitemapUrl, readSitemap)).entries;\n\n const snapshotPath = resolve(values.snapshot as string);\n const p = plan({\n config: { ...config, sitemapUrl },\n current,\n previous: readSnapshot(snapshotPath),\n explicitUrls,\n all: values.all as boolean,\n });\n\n console.log(formatPlan(p));\n\n if (p.blocked) {\n // Not a failure. Running this against a `noindex` environment is a coherent\n // thing for a deploy script to do — the config said not to index it, and the\n // command honoured that. Failing the build would punish the correct setup.\n return;\n }\n if (values.dry) {\n console.log('\\n(--dry: nothing was submitted)');\n return;\n }\n\n const account = loadServiceAccount();\n let failed = false;\n\n // ── --check: read-only, and nothing else runs ───────────────────────────────\n if (values.check) {\n if (!p.searchConsole.siteUrl)\n fail('--check needs seo.indexing.searchConsole.siteUrl (the Search Console property)');\n if (!account)\n fail(\n '--check needs a Search Console service account in VITOPS_GSC_SERVICE_ACCOUNT or GOOGLE_APPLICATION_CREDENTIALS',\n );\n if (p.check.length === 0)\n fail('--check needs seo.indexing.priorityUrls — the pages whose indexing matters');\n\n const token = await getAccessToken(account);\n console.log(`\\nInspecting ${p.check.length} priority URL(s):`);\n for (const url of p.check) {\n const r = await inspectUrl(token, p.searchConsole.siteUrl, url);\n if (r.error) {\n console.log(` ? ${url} — ${r.error}`);\n failed = true;\n } else {\n console.log(\n ` ${r.indexed ? '✓' : '✗'} ${url} — ${r.coverageState ?? r.verdict ?? 'unknown'}`,\n );\n if (!r.indexed) failed = true;\n }\n }\n if (failed) {\n console.error('\\n✖ one or more priority URLs are not indexed');\n process.exit(1);\n }\n console.log('\\n✓ every priority URL is indexed');\n return;\n }\n\n // ── IndexNow ────────────────────────────────────────────────────────────────\n if (p.indexNow.enabled) {\n const { key, keyLocation } = p.indexNow;\n const check = await verifyKeyFile(keyLocation!, key!);\n if (!check.ok) {\n // Hard stop rather than \"submit and hope\": IndexNow answers 202 and then\n // discards the batch when the key doesn't verify, so submitting anyway\n // would print a success it did not earn.\n console.error(`✖ IndexNow key file check failed — ${check.reason}`);\n console.error(` Write it with: vitops indexing --write-key <public dir>`);\n failed = true;\n } else {\n let sent = 0;\n for (const batch of p.indexNow.batches) {\n const r = await submitBatch(p.indexNow, batch);\n if (!r.ok) {\n console.error(`✖ IndexNow: ${r.message ?? r.status}`);\n failed = true;\n break;\n }\n sent += r.urls;\n }\n if (!failed) console.log(`✓ IndexNow: ${sent} URL(s) submitted`);\n }\n }\n\n // ── Search Console ──────────────────────────────────────────────────────────\n if (p.searchConsole.enabled) {\n if (!account) {\n console.log(\n '· Search Console: skipped — no VITOPS_GSC_SERVICE_ACCOUNT / GOOGLE_APPLICATION_CREDENTIALS',\n );\n } else {\n const token = await getAccessToken(account);\n const r = await submitSitemap(token, p.searchConsole.siteUrl!, sitemapUrl);\n if (r.ok) console.log(`✓ Search Console: sitemap resubmitted`);\n else {\n console.error(`✖ Search Console: ${r.message}`);\n failed = true;\n }\n }\n }\n\n if (failed) process.exit(1);\n\n /*\n * The snapshot is written last, and only on success.\n *\n * Writing it eagerly would record URLs as notified that never were — and since\n * the next run diffs against it, a single transient 503 would drop those pages\n * from every future run, silently and permanently.\n */\n if (current.length > 0) {\n writeSnapshot(snapshotPath, toSnapshot(sitemapUrl, current, new Date().toISOString()));\n console.log(` state → ${relative(process.cwd(), snapshotPath)}`);\n }\n if (p.check.length > 0)\n console.log(\n `\\n Indexing is not instant. Run \\`vitops indexing --check\\` in a day or two to see what Google actually did.`,\n );\n}\n\n// This CLI's own version (dist/cli.mjs → package-root package.json).\nfunction cliVersion(): string {\n try {\n return (\n JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version ??\n '0.0.0'\n );\n } catch {\n return '0.0.0';\n }\n}\n\n// The framework asset root shipped inside @getvitops/generator (holds the Bricks\n// PHP that generateDocs reads for the element reference).\nfunction generatorAssetsDir(): string {\n const pkg = createRequire(import.meta.url).resolve('@getvitops/generator/package.json');\n return join(dirname(pkg), 'assets');\n}\n\nconst AGENTS_START = '<!-- vitops:start -->';\nconst AGENTS_END = '<!-- vitops:end -->';\n\n/** Print a live reference doc (rendered from the project's config) to stdout. */\nfunction cmdDocs(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n theme: { type: 'string' },\n all: { type: 'boolean', default: false },\n },\n allowPositionals: true,\n });\n const topic = positionals[0];\n const topicList = () =>\n Object.entries(TOPICS)\n .map(([name, t]) => ` ${name.padEnd(10)} ${t.summary}`)\n .join('\\n');\n if (!topic && !values.all) {\n console.log(\n `vitops docs <topic> — print a live design-system reference to stdout\\n\\nTopics:\\n${topicList()}\\n\\nAlso: vitops docs --all (every topic, concatenated)`,\n );\n return;\n }\n if (topic && !TOPICS[topic]) fail(`unknown topic \"${topic}\". Valid topics:\\n${topicList()}`);\n const ds = loadConfig(resolve(values.input as string), values.theme as string | undefined);\n const docs = generateDocs(ds, generatorAssetsDir());\n const paths = values.all\n ? Object.values(TOPICS).map((t) => t.path)\n : [TOPICS[topic as string]!.path];\n process.stdout.write(paths.map((p) => docs[p]).join('\\n'));\n}\n\nfunction cmdLint(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n theme: { type: 'string' },\n format: { type: 'string', short: 'f', default: 'bricks' },\n src: { type: 'string', short: 's', default: 'src' },\n // Suggestions are advisory: a reuse rule that failed CI the day it shipped\n // would be a worse defect than the drift it reports.\n strict: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n const format = values.format as StylesheetFormat;\n if (!LINT_FORMATS.has(format))\n fail(`unknown format \"${format}\" (expected: bricks | css | tailwind)`);\n const ds = loadConfig(resolve(values.input as string), values.theme as string | undefined);\n const srcDir = resolve(values.src as string);\n if (!existsSync(srcDir)) fail(`source directory not found: ${srcDir}`);\n\n // Ask the generator what it actually emits rather than re-deriving the rules\n // here — a linter that models the vocabulary separately just drifts into\n // reporting classes that do exist.\n const palette = expandPalette(ds.colors.palette);\n const roleClasses = roleColorUtilities(\n Object.entries(ds.colors.roles).map(([role, spec]) =>\n functionalRole(role, roleHue(spec), palette[roleHue(spec)]!, roleKind(spec)),\n ),\n ds.colors.utilities ?? ['bg', 'text', 'icon', 'border'],\n ).map((u) => u.cls);\n\n const files = scanFiles(srcDir);\n // `.css` is not in the default extension set — the class linter reads markup,\n // not stylesheets — so the reuse rules get their own pass. Component files are\n // scanned twice on purpose: once for their class attributes, once for any\n // `<style>` block, which is where hand-rolled layout tends to accumulate.\n const cssFiles = [...files, ...scanFiles(srcDir, { exts: ['.css'] })];\n const findings = [\n ...lintSource(files, vocabulary(ds, roleClasses), format),\n ...lintCss(cssFiles, format),\n ];\n\n if (!findings.length) {\n console.log(\n `✓ no unresolvable framework classes in ${values.src} ` +\n `(${files.length} file${files.length === 1 ? '' : 's'} scanned)`,\n );\n return;\n }\n\n const errors = findings.filter((f) => f.severity === 'error');\n const suggestions = findings.filter((f) => f.severity === 'suggestion');\n // Errors first: a class that resolves to nothing is a defect, a reuse\n // suggestion is a judgement call, and mixing them buries the former.\n for (const f of [...errors, ...suggestions]) {\n console.error(`${f.file}:${f.line} ${f.severity === 'error' ? f.cls : `(${f.cls})`}`);\n console.error(` ${f.reason}`);\n if (f.suggestion) console.error(` try: ${f.suggestion}`);\n }\n\n const count = (n: number, one: string, many: string) => `${n} ${n === 1 ? one : many}`;\n const parts: string[] = [];\n if (errors.length) parts.push(count(errors.length, 'unresolvable class', 'unresolvable classes'));\n if (suggestions.length) parts.push(count(suggestions.length, 'suggestion', 'suggestions'));\n const failing = errors.length > 0 || (values.strict === true && suggestions.length > 0);\n console.error(\n `\\n${failing ? '✖' : '!'} ${parts.join(', ')} in ` +\n `${files.length} file${files.length === 1 ? '' : 's'}` +\n (suggestions.length && !values.strict ? ' (suggestions do not fail; use --strict)' : ''),\n );\n if (failing) process.exit(1);\n}\n\nfunction cmdAgents(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n input: { type: 'string', short: 'i', default: 'design-system.json' },\n theme: { type: 'string' },\n out: { type: 'string', short: 'o', default: 'AGENTS.md' },\n 'docs-dir': { type: 'string' },\n },\n allowPositionals: false,\n });\n const inputRel = values.input as string;\n // The command is pure wiring: validate the config when present (fail on an\n // invalid one), but don't require it — docs are rendered live by `vitops docs`.\n let ds: DesignSystem | null = null;\n if (existsSync(resolve(inputRel)))\n ds = loadConfig(resolve(inputRel), values.theme as string | undefined);\n else console.warn(` ⚠ ${inputRel} not found — run \\`vitops init\\` to scaffold one`);\n\n // Explicit --docs-dir = legacy layout: write the docs bundle as files, no skill.\n const legacyDocsDir = values['docs-dir'] as string | undefined;\n let summary: string;\n let pointer: string[];\n\n if (legacyDocsDir) {\n if (!ds) fail(`--docs-dir needs a config to render docs from (${inputRel} not found)`);\n let docs: Record<string, string> = {};\n try {\n docs = generateDocs(ds, generatorAssetsDir());\n } catch (err) {\n fail(`could not generate docs: ${(err as Error).message}`);\n }\n for (const [rel, content] of Object.entries(docs)) {\n const p = resolve(legacyDocsDir, rel);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, content);\n }\n summary = `wrote ${Object.keys(docs).length} docs to ${legacyDocsDir}/`;\n pointer = [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Reference docs (regenerate with \\`vitops agents --docs-dir ${legacyDocsDir}\\`): \\`${legacyDocsDir}/css/classes.md\\`,`,\n `\\`${legacyDocsDir}/authoring.md\\`, \\`${legacyDocsDir}/formats.md\\`, \\`${legacyDocsDir}/bricks/elements.md\\`.`,\n ];\n } else {\n // Link the packaged skill (ships inside @getvitops/cli) into the agent\n // discovery locations; nothing is generated into the repo.\n const target = findSkillTarget(process.cwd());\n if (target) {\n for (const dir of ['.agents', '.claude']) {\n const warn = linkSkill(resolve(dir, 'skills', SKILL_NAME), target);\n if (warn) console.warn(` ⚠ ${warn}`);\n }\n summary = `linked {.agents,.claude}/skills/${SKILL_NAME} → ${target}`;\n } else {\n console.warn(\n ` ⚠ could not find node_modules/@getvitops/cli/skill — install @getvitops/cli as a devDependency, then re-run \\`vitops agents\\``,\n );\n summary = 'no skill linked';\n }\n pointer = [\n 'Prefer the framework’s utility + component classes over hand-written CSS.',\n `Full design-system context lives in the \\`${SKILL_NAME}\\` skill (linked from`,\n '`.agents/skills/` and `.claude/skills/` into the installed `@getvitops/cli`;',\n 're-link with `vitops agents` if the links are deleted — they survive version',\n 'bumps). Reference docs print live, from this project’s config, via:',\n '',\n ...Object.entries(TOPICS).map(([name, t]) => `- \\`vitops docs ${name}\\` — ${t.summary}`),\n ];\n }\n\n const block = [\n AGENTS_START,\n '## Vitops design system',\n '',\n `Styled with the Vitops design system (\\`@getvitops/*\\`); tokens live in \\`${inputRel}\\`.`,\n 'Generate output with the CLI:',\n '',\n '- `vitops generate --format tailwind --out src/styles` — Tailwind v4 / Astro',\n '- `vitops generate --format css --out dist` — standalone CSS',\n '- `vitops generate --format bricks --out <theme>/dist` — WordPress / Bricks',\n '- `vitops generate --format design --out .` — `DESIGN.md`, the agent-facing brief',\n '- `vitops init` · `vitops validate` · `vitops favicon`',\n '',\n ...pointer,\n '',\n `<!-- regenerate: vitops agents · @getvitops/cli@${cliVersion()} -->`,\n AGENTS_END,\n ].join('\\n');\n\n const outPath = resolve(values.out as string);\n let next: string;\n let action: string;\n if (existsSync(outPath)) {\n const cur = readFileSync(outPath, 'utf8');\n const s = cur.indexOf(AGENTS_START);\n const e = cur.indexOf(AGENTS_END);\n if (s !== -1 && e !== -1 && e > s) {\n next = cur.slice(0, s) + block + cur.slice(e + AGENTS_END.length);\n action = 'updated block in';\n } else {\n next = cur.replace(/\\s*$/, '') + '\\n\\n' + block + '\\n';\n action = 'appended block to';\n }\n } else {\n mkdirSync(dirname(outPath), { recursive: true });\n next = block + '\\n';\n action = 'created';\n }\n writeFileSync(outPath, next);\n console.log(`✓ ${action} ${values.out} + ${summary}`);\n}\n\nasync function main() {\n const [command, ...rest] = process.argv.slice(2);\n if (!command || command === '-h' || command === '--help' || command === 'help') {\n console.log(HELP);\n return;\n }\n switch (command) {\n case 'generate':\n return cmdGenerate(rest);\n case 'init':\n return cmdInit(rest);\n case 'validate':\n return cmdValidate(rest);\n case 'favicon':\n return cmdFavicon(rest);\n case 'agents':\n return cmdAgents(rest);\n case 'docs':\n return cmdDocs(rest);\n case 'lint':\n return cmdLint(rest);\n case 'legal':\n return cmdLegal(rest);\n case 'icons':\n return cmdIcons(rest);\n case 'indexing':\n return cmdIndexing(rest);\n case 'media':\n return cmdMedia(rest);\n default:\n fail(\n `unknown command \"${command}\" (expected: generate | init | validate | favicon | agents | docs | lint | legal | icons | indexing | media). Try: vitops --help`,\n );\n }\n}\n\nmain().catch((err) => fail((err as Error).message));\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI for the Vitops design-system generator: generate, init, and validate a design-system.json.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@getvitops/generator": "2.
|
|
26
|
-
"@getvitops/utils": "2.
|
|
25
|
+
"@getvitops/generator": "2.1.0",
|
|
26
|
+
"@getvitops/utils": "2.1.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"vitest": "^4.1.9"
|
package/skill/SKILL.md
CHANGED
|
@@ -97,6 +97,22 @@ where no provider covers the family.
|
|
|
97
97
|
`legal.privacyPolicy.processors`.
|
|
98
98
|
Generated from config, not legal advice — always tell the user to have it reviewed
|
|
99
99
|
before publishing.
|
|
100
|
+
- `vitops indexing` — tell search engines a deploy happened, from a **site config**'s
|
|
101
|
+
`seo.indexing` block. Run `--dry` first: it prints the full plan and makes no requests.
|
|
102
|
+
**Be accurate about what this does, because the obvious assumption is wrong.** Google
|
|
103
|
+
has no API that requests indexing — the Search Console button is not exposed anywhere,
|
|
104
|
+
URL Inspection is read-only, and the sitemap ping endpoint was removed in 2023. So:
|
|
105
|
+
it resubmits the sitemap through the Search Console API, pings **IndexNow**
|
|
106
|
+
(Bing, Yandex, Naver, Seznam, Yep — _not_ Google), and `--check` verifies afterwards
|
|
107
|
+
whether Google actually indexed `seo.indexing.priorityUrls`, exiting non-zero if not.
|
|
108
|
+
Never tell a user this makes Google re-index faster; it automates the sanctioned steps
|
|
109
|
+
and makes the result visible.
|
|
110
|
+
Two things decide whether it works at all: the sitemap needs real per-page `<lastmod>`
|
|
111
|
+
(wire `gitLastmod()` from `@getvitops/astro` into the `sitemap` option — without it
|
|
112
|
+
only added/removed pages are detectable, never an edit), and `.vitops/` must persist
|
|
113
|
+
between runs or every run submits everything. Google's **Indexing API is deliberately
|
|
114
|
+
not wired**: it is scoped to `JobPosting`/`BroadcastEvent`, and using it for ordinary
|
|
115
|
+
pages violates its terms — don't suggest it as a workaround.
|
|
100
116
|
- `vitops agents` — (re-)link this skill into `.agents/skills/` + `.claude/skills/` and
|
|
101
117
|
refresh the AGENTS.md pointer block. The links point into the installed package and
|
|
102
118
|
survive version bumps; re-run only if they were deleted.
|