@getvitops/vite 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,212 @@
1
1
  # @getvitops/vite
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bf453b0: Anywhere the toolchain takes a config, that file may now be a `design-system.json` **or** the larger
8
+ site config that embeds one.
9
+
10
+ If you keep your tokens inside a `company.json` / `site.json`, you had to maintain a second file for
11
+ the tooling's sake — or duplicate your whole token set, because a site config must carry a complete
12
+ `designSystem`. Now you point the same option at the file you already have:
13
+
14
+ ```js
15
+ // astro.config.mjs
16
+ getvitops({ css: { input: 'company.json', format: 'css' } });
17
+ ```
18
+
19
+ ```
20
+ vitops generate --input company.json --format css --out dist
21
+ vitops lint --input company.json --src src
22
+ ```
23
+
24
+ The design system is taken from `designSystem.themes[theme]` with its `extends` chain resolved —
25
+ `defaultTheme`, else `default`, else whatever `--theme` / the `theme` option names. Nothing changes
26
+ for a plain `design-system.json`; the two are told apart by shape, not by filename, so you can call
27
+ the file anything.
28
+
29
+ **A site config also supplies the site-level facts generation reads**, so the path is declared once
30
+ rather than per option:
31
+ - `designSystem.defaultColorScheme: "system"` — emits the `prefers-color-scheme` block, which is
32
+ what makes `<color-scheme-toggle>`'s System position do anything.
33
+ - `legal.*.enabled` — renders the documents. `legal: {}` is now the whole declaration; `legal.input`
34
+ is optional.
35
+ - `icons.sprite` — builds `icons.svg`.
36
+ - `fonts` — `fonts: true` reads the families from it.
37
+
38
+ Each of those is already gated on a field in that config, so nothing new appears unless the config
39
+ asks for it. An explicit `site` option still wins when the two are genuinely different files.
40
+
41
+ Also:
42
+ - **`vitops validate` routes on the file's shape.** Pointed at a site config it used to report a
43
+ single `unrecognized_keys` for `designSystem` and nothing about the file's actual contents — a
44
+ wrong answer that reads like a right one. It now validates a site config as one, including the
45
+ cross-field integrity JSON Schema can't express, and checks every theme resolves to a complete
46
+ design system (`validateSite` only checked that the `extends` chain resolved, not what it resolved
47
+ to).
48
+ - **`generate()` gained `theme` and `siteEnv`.** The A/B variant for `siteEnv` is applied before the
49
+ theme is selected, so a variant can override tokens.
50
+ - **The theme editor's Save to source follows the design system into a site config.** Its patch is
51
+ design-system-relative, so the dev server merges into that subtree and writes only the surrounding
52
+ file whole. It locates the subtree in the raw on-disk object rather than the normalised one, so an
53
+ author who wrote either `designSystem` shorthand still gets their own keys edited.
54
+
55
+ - bf453b0: **Breaking (site config):** `designSystem` is now an object, and the toggle's "System" position works.
56
+
57
+ `<color-scheme-toggle>` has always shipped three segments, and "System" did nothing. It removes the
58
+ theme attribute, and with no `prefers-color-scheme` block in the generated CSS the page fell straight
59
+ through to light on every machine — so a third of the control was inert, and a no-JS page could never
60
+ follow the OS at all.
61
+
62
+ The fix is a second, opt-in copy of the dark delta under
63
+ `@media (prefers-color-scheme: dark)`, scoped to "no explicit choice has been made". An explicit
64
+ light choice still wins over a dark OS. It costs **+303 B gzipped** on the colour layer — the block
65
+ repeats only the tokens whose dark value differs, and two identical runs of text compress to almost
66
+ nothing. It is opt-in because switching it on visibly flips an existing site dark for dark-OS
67
+ visitors.
68
+
69
+ Turning it on is a site-level fact, which needed somewhere to live:
70
+
71
+ ```jsonc
72
+ "designSystem": {
73
+ "themes": { "default": { … }, "elegant": { "extends": "default", … } },
74
+ "defaultTheme": "default",
75
+ "defaultColorScheme": "system" // 'light' | 'dark' | 'system'
76
+ }
77
+ ```
78
+
79
+ **What changed.** `designSystem` was a bare map of themeName → design system, so _every_ key was a
80
+ theme name and there was nowhere to put a system-wide field without it colliding with a theme of that
81
+ name. The map moves under `themes`, and `defaultTheme` + `defaultColorScheme` move inside the block.
82
+ `respectSystemPreference` is gone — `defaultColorScheme: "system"` says the same thing, and the two
83
+ were always read together, so the incoherent combination is no longer expressible.
84
+
85
+ **Migration is automatic at runtime.** `resolveSiteConfig` accepts all three spellings — the
86
+ canonical shape, a bare theme map (the old schema), and a bare design system written inline. But
87
+ `site.schema.json` is published to a stable URL, so an editor pinned to `$schema` will flag the old
88
+ shape; update it when convenient.
89
+
90
+ One behavioural change worth knowing: shorthand normalisation now runs **before** the A/B variant
91
+ merge, not after. Previously an `abTesting` override's key path depended on which shorthand the base
92
+ config happened to use, so the same patch landed in different places in two otherwise-equivalent
93
+ configs. Overrides now always address `designSystem.themes.<name>`.
94
+
95
+ Also: `getvitops({ site: { input } })` gives the Astro integration one place to name the site config
96
+ — `legal`, `fonts` and the colour scheme all read from it — and `css.systemColorScheme` sets the
97
+ appearance directly for consumers who have no site config.
98
+
99
+ Light/dark remains **derived**, not a theme: `functionalRole()` builds both appearances from one ramp,
100
+ which is what lets the contrast contract check both at build time and what gives every consumer a
101
+ working dark mode without authoring one. `themes` is for authored variants.
102
+
103
+ - 04a51d8: Icons: one semantic vocabulary across icon sets, with the bundle derived from your source.
104
+
105
+ Name icons by meaning — `<Icon name="menu" />` resolves to `fa7-solid:bars`, `ph:list` or
106
+ `lucide:menu` depending on the set you configure, so swapping sets is a config edit rather than a
107
+ find-and-replace. A name containing `:` still passes through untouched, which is the escape hatch
108
+ for a set-specific glyph.
109
+
110
+ **New `icons` option on `getvitops()`.** Configures the sets once per site and, under
111
+ `output: 'server'`, derives astro-icon's `include` map by scanning your source — the list most
112
+ projects end up maintaining by hand. On a static build no `include` is passed at all, because
113
+ astro-icon is already zero-config there and a list could only drop a glyph the scan couldn't see.
114
+ Names you declare that don't resolve fail the build; names only the scan found just warn; names
115
+ computed at runtime are reported with file and line so you can declare them.
116
+
117
+ **New `<Icon />` component**, and a real fix with it: `Popover`, `Details` and `Drawer` imported
118
+ `astro-icon/components` at module scope, so the _optional_ peer was resolved whether or not an icon
119
+ ever rendered — hard-failing anyone who hadn't installed it. Engines now load dynamically. The
120
+ per-component `iconResolver` prop still works but is deprecated in favour of the integration option.
121
+
122
+ **New SVG sprite** (`icons.sprite: true` in your site config) for consumers that can't run an icon
123
+ integration — Bricks/WordPress, EmDash renderers, plain HTML. `<use href="…/icons.svg#ph--list">`,
124
+ no JavaScript and no icon-API request. Every semantic name also gets a set-independent `icon-<name>`
125
+ alias, so sprite markup survives an icon-set change. WordPress gets `vitops_icon()`, a
126
+ `[vitops_icon]` shortcode and a **Vitops → Icon** Bricks element.
127
+
128
+ **New `vitops icons`** command: reports which icons your source uses, which names couldn't be
129
+ resolved, and which are computed at runtime; `--sprite` builds the sprite, `--json` for CI.
130
+
131
+ **Renamed, and completed to all four directions.** Chevrons and arrows are the
132
+ one family whose _meaning is_ its direction, and that direction is physical, not
133
+ logical — a chevron in a details marker points down in every writing mode. So they
134
+ are named for where they point: `expand-more`/`expand-less` →
135
+ `chevron-down`/`chevron-up`, and `arrow-forward`/`arrow-back` →
136
+ `arrow-right`/`arrow-left`. `chevron-left`/`chevron-right` keep their names.
137
+ `arrow-up`/`arrow-down` are new, so both families now cover all four directions.
138
+ `lightning` is new.
139
+
140
+ If you passed any of the old names to `<Icon />`, `resolveIcon` or an `icons`
141
+ config, update them. They fail loudly rather than silently: an unresolvable
142
+ declared name throws, and `vitops icons` reports scanned ones.
143
+
144
+ **Fixed three Font Awesome mappings that named no real glyph** and so rendered an
145
+ empty box: `login`/`logout` were mapped to `login`/`logout` (Font Awesome calls
146
+ them `right-to-bracket`/`right-from-bracket`) and `backup` to `backup`
147
+ (`cloud-arrow-up`). Every value in all four UI sets is now checked against the
148
+ installed collection.
149
+
150
+ **Phosphor (`ph`) joins the semantic map**, with all 83 names verified against the real icon set.
151
+ Phosphor keeps every weight in one collection and varies the name (`list`, `list-bold`), unlike Font
152
+ Awesome's per-weight collections, so `resolveIcon` and `generateIconInclude` gained a `weight`
153
+ option for sets shaped that way.
154
+
155
+ Fixes: `site.icons` was a closed object, so any icon collection not in its hand-written key list was
156
+ silently dropped during validation — your config passed and the icons never bundled. It accepts any
157
+ collection name now.
158
+
159
+ - bf453b0: Maskable favicons are now opaque, so they stop rendering as a logo in a black box.
160
+
161
+ `icon-mask.png` is declared `purpose: "maskable"` and `apple-touch-icon.png` is linked on every
162
+ page. Both sit a deliberately-inset logo on a larger canvas — that inset **is** the maskable safe
163
+ zone and was always correct — but the canvas was filled with `alpha: 0`. From any transparent
164
+ source that left 36% of `icon-mask.png` and 40% of `apple-touch-icon.png` transparent, on two files
165
+ whose entire contract is full bleed: the OS crops them to its own shape and composites the rest onto
166
+ whatever it likes, usually black. iOS discards alpha on the apple-touch-icon outright.
167
+
168
+ `backgroundColor` was already the obvious input for this and already plumbed — as far as the web
169
+ manifest, and no further. So the raster and the manifest disagreed about the very colour meant to
170
+ sit behind the icon.
171
+
172
+ Now:
173
+ - those two outputs are composited onto `backgroundColor`, defaulting to `#ffffff` — the same
174
+ default the manifest's `background_color` already used;
175
+ - `favicon.svg`, `icon-{16,32,192,512}.png` and `favicon.ico` keep the source's transparency, since
176
+ none of them is maskable;
177
+ - a transparent source with no `backgroundColor` set now warns, rather than silently inheriting
178
+ white — a dark logo would lose against it;
179
+ - `icon-192`/`icon-512` declare `purpose: "any"` explicitly. Omitting it was legal (unset means
180
+ "any"), but with a maskable present and nothing claiming "any", some launchers picked the maskable
181
+ — the one with the safe-zone inset — for slots that wanted a plain icon.
182
+
183
+ `vitops favicon` gained `--background <hex>`; it previously had no way to set this at all.
184
+ `getvitops({ favicon })` and the Vite plugin now forward the option they were already accepting.
185
+
186
+ ### Patch Changes
187
+
188
+ - Updated dependencies [4756788]
189
+ - Updated dependencies [bf453b0]
190
+ - Updated dependencies [bf453b0]
191
+ - Updated dependencies [bf453b0]
192
+ - Updated dependencies [bf453b0]
193
+ - Updated dependencies [bf453b0]
194
+ - Updated dependencies [bf453b0]
195
+ - Updated dependencies [bf453b0]
196
+ - Updated dependencies [bf453b0]
197
+ - Updated dependencies [04a51d8]
198
+ - Updated dependencies [bf453b0]
199
+ - Updated dependencies [bf453b0]
200
+ - Updated dependencies [bf453b0]
201
+ - Updated dependencies [bf453b0]
202
+ - Updated dependencies [bf453b0]
203
+ - Updated dependencies [bf453b0]
204
+ - Updated dependencies [bf453b0]
205
+ - Updated dependencies [bf453b0]
206
+ - Updated dependencies [bf453b0]
207
+ - @getvitops/generator@2.0.0
208
+ - @getvitops/utils@2.0.0
209
+
3
210
  ## 1.0.0
4
211
 
5
212
  ### Minor Changes
package/README.md CHANGED
@@ -10,7 +10,7 @@ import vitops from '@getvitops/vite';
10
10
  export default {
11
11
  plugins: [
12
12
  vitops({
13
- input: 'design-system.json', // default
13
+ input: 'design-system.json', // default — or a site config (company.json) that embeds one
14
14
  format: 'tailwind', // 'bricks' | 'css' | 'tailwind' (default: 'tailwind')
15
15
  out: 'src/styles', // default
16
16
  }),
@@ -18,6 +18,11 @@ export default {
18
18
  };
19
19
  ```
20
20
 
21
+ `input` accepts either config kind. Pointed at a site config it builds
22
+ `designSystem.themes[theme]` (`theme` option; default the config's `defaultTheme`, else `default`)
23
+ and reads the site-level facts from the same file — so `legal: {}` needs no `input` of its own, and
24
+ the theme editor's **Save to source** writes back into that theme rather than the file root.
25
+
21
26
  Then import the generated stylesheet (Tailwind v4):
22
27
 
23
28
  ```css
package/dist/index.d.mts CHANGED
@@ -2,6 +2,20 @@ import { Format, LegalOutput } from "@getvitops/generator";
2
2
  import { Plugin } from "vite";
3
3
 
4
4
  //#region src/index.d.ts
5
+ /**
6
+ * Where the design system lives inside the config file on disk.
7
+ *
8
+ * `[]` for a `design-system.json` — the file *is* the design system. For a site
9
+ * config it is under `designSystem`, and this reads the **raw** object rather
10
+ * than the resolved one because the editor's write-back has to land where the
11
+ * author actually put it. `resolveSiteConfig` normalises the two shorthands in
12
+ * memory; a writer that assumed the canonical shape would grow a second
13
+ * `themes` key beside the author's bare map and silently stop editing anything.
14
+ */
15
+ declare function designSystemPath(raw: unknown, theme?: string): string[];
16
+ declare const getAt: (obj: unknown, path: string[]) => unknown;
17
+ /** Return a copy of `obj` with `path` replaced by `value`. Creates missing objects. */
18
+ declare function setAt(obj: unknown, path: string[], value: unknown): unknown;
5
19
  interface VitopsFaviconOptions {
6
20
  /** Source SVG or PNG. */
7
21
  source: string;
@@ -17,8 +31,22 @@ interface VitopsFaviconOptions {
17
31
  backgroundColor?: string;
18
32
  }
19
33
  interface VitopsPluginOptions {
20
- /** Path to the design-system.json. Default: 'design-system.json'. */
34
+ /**
35
+ * Path to the config. Default: `'design-system.json'`.
36
+ *
37
+ * May be a `design-system.json` **or** the larger site config that embeds one
38
+ * (`company.json` / `site.json`) — told apart by shape. Pointing it at a site
39
+ * config also supplies `site` below, so a consumer who keeps their tokens
40
+ * there declares the path once instead of three times.
41
+ */
21
42
  input?: string;
43
+ /**
44
+ * Which `designSystem.themes` entry to build, when `input` is a site config.
45
+ * Default: the config's `defaultTheme`, else `default`.
46
+ *
47
+ * Also the theme `<wc-theme-editor>`'s **Save to source** writes back into.
48
+ */
49
+ theme?: string;
22
50
  /** Output target. Default: 'tailwind' (the EmDash/Astro case). */
23
51
  format?: Format;
24
52
  /** Directory to write generated output into. Default: 'src/styles'. */
@@ -32,18 +60,52 @@ interface VitopsPluginOptions {
32
60
  * it can't race the first generation and can't go stale on regeneration.
33
61
  */
34
62
  editorManifestDir?: string;
63
+ /**
64
+ * Directory to mirror the generated `icons.svg` into as `vitops/icons.svg` —
65
+ * the URL `<Icon />` targets under the `sprite` engine. Same reasoning as
66
+ * `editorManifestDir`: copied inside the generate pass, so it can't race a
67
+ * cold start or go stale after a config edit. The *href* is a plain string
68
+ * decided at config:setup; only the bytes arrive here.
69
+ */
70
+ spriteDir?: string;
35
71
  /**
36
72
  * Also render the site's legal documents on build, and re-render when the site
37
73
  * config changes.
38
74
  *
39
- * Its own option rather than part of the design-system pass because it reads a
40
- * different config kind — a `SiteConfig`, not a `design-system.json`.
75
+ * Its own option because it is an *output* you opt into — but its `input` is
76
+ * optional, defaulting to `site.input`, then to `input` when that is itself a
77
+ * site config. `legal: {}` is therefore the whole declaration in the common case.
41
78
  */
42
79
  legal?: VitopsLegalOptions;
80
+ /**
81
+ * The site config, for the parts of generation that depend on site-level facts
82
+ * rather than on the design system — currently `designSystem.defaultColorScheme`, which
83
+ * decides whether the colour layer carries a `prefers-color-scheme` block.
84
+ *
85
+ * Only needed when the site config is a *different* file from `input`; when
86
+ * `input` is itself a site config, that one is used. Watched, so an edit
87
+ * regenerates.
88
+ */
89
+ site?: VitopsSiteOptions;
90
+ /**
91
+ * Emit the `prefers-color-scheme: dark` block. Normally read from the site
92
+ * config's `designSystem.defaultColorScheme: "system"`; set here it wins, for consumers with no
93
+ * site config.
94
+ */
95
+ systemColorScheme?: boolean;
43
96
  }
44
- interface VitopsLegalOptions {
97
+ interface VitopsSiteOptions {
45
98
  /** Path to the site config (JSON). */
46
99
  input: string;
100
+ /** Environment whose A/B variant applies. Default: 'production'. */
101
+ siteEnv?: string;
102
+ }
103
+ interface VitopsLegalOptions {
104
+ /**
105
+ * Path to the site config (JSON). Optional: defaults to `site.input`, then to
106
+ * the plugin's own `input` when that is itself a site config.
107
+ */
108
+ input?: string;
47
109
  /** Directory to write documents into. Default: 'src/content/legal'. */
48
110
  out?: string;
49
111
  /** Output format. Default: 'md', which suits an Astro content collection. */
@@ -53,5 +115,5 @@ interface VitopsLegalOptions {
53
115
  }
54
116
  declare function vitops(options?: VitopsPluginOptions): Plugin;
55
117
  //#endregion
56
- export { VitopsFaviconOptions, VitopsLegalOptions, VitopsPluginOptions, vitops as default };
118
+ export { VitopsFaviconOptions, VitopsLegalOptions, VitopsPluginOptions, VitopsSiteOptions, vitops as default, designSystemPath, getAt, setAt };
57
119
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;UA4EiB,oBAAA;;EAEf,MAAA;EAFmC;EAInC,YAAA;EAJmC;EAMnC,GAAA;EAFA;EAIA,IAAA;EAAA;EAEA,UAAA;EAEA;EAAA,eAAA;AAAA;AAAA,UAGe,mBAAA;EAAmB;EAElC,KAAA;EAES;EAAT,MAAA,GAAS,MAAA;EAmBD;EAjBR,GAAA;EAiB0B;EAf1B,OAAA,GAAU,oBAAA;EAJV;;;;;;EAWA,iBAAA;EAQQ;;AAAkB;AAG5B;;;;EAHE,KAAA,GAAQ,kBAAA;AAAA;AAAA,UAGO,kBAAA;EAMN;EAJT,KAAA;EAMO;EAJP,GAAA;EAOsB;EALtB,MAAA,GAAS,WAAW;;EAEpB,OAAA;AAAA;AAAA,iBAGsB,MAAA,CAAO,OAAA,GAAS,mBAAA,GAA2B,MAAM"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;AAqCA;;;;AAAgE;AAWhE;;;iBAXgB,gBAAA,CAAiB,GAAA,WAAc,KAAiB;AAAA,cAWnD,KAAA,GAAS,GAAA,WAAc,IAAc;AAIlD;AAAA,iBAAgB,KAAA,CAAM,GAAA,WAAc,IAAA,YAAgB,KAAA;AAAA,UAyDnC,oBAAA;EAzDI;EA2DnB,MAAA;EA3DkC;EA6DlC,YAAA;EA7DgE;EA+DhE,GAAA;EANe;EAQf,IAAA;;EAEA,UAAA;EARA;EAUA,eAAA;AAAA;AAAA,UAGe,mBAAA;EALf;;;AAEe;AAGjB;;;;EASE,KAAA;EAqCQ;;;;;;EA9BR,KAAA;EAES;EAAT,MAAA,GAAS,MAAA;EAIT;EAFA,GAAA;EASA;EAPA,OAAA,GAAU,oBAAA;EAwBV;;;;;;EAjBA,iBAAA;EAoCe;;;;AAIR;AAGT;;EAnCE,SAAA;EA4CoB;;;;;;;AAEb;EArCP,KAAA,GAAQ,kBAAA;EAwCoB;;;;;;;AAA2C;;EA9BvE,IAAA,GAAO,iBAAA;;;;;;EAMP,iBAAA;AAAA;AAAA,UAGe,iBAAA;;EAEf,KAAA;;EAEA,OAAO;AAAA;AAAA,UAGQ,kBAAA;;;;;EAKf,KAAA;;EAEA,GAAA;;EAEA,MAAA,GAAS,WAAW;;EAEpB,OAAA;AAAA;AAAA,iBAGsB,MAAA,CAAO,OAAA,GAAS,mBAAA,GAA2B,MAAM"}
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { generate, generateLegal, resolveSiteConfig, validate } from "@getvitops/generator";
3
+ import { generate, generateLegal, isSiteConfig, resolveSiteConfig, validate } from "@getvitops/generator";
4
4
  import { generateFavicons, writeFaviconManifest } from "@getvitops/utils";
5
5
 
6
6
  //#region src/index.ts
7
7
  /**
8
8
  * @getvitops/vite — a Vite plugin that generates Vitops design-system output from a
9
- * `design-system.json` during a Vite/Astro (EmDash) build, and hot-regenerates when
10
- * the config changes in dev.
9
+ * `design-system.json` (or from the larger site config that embeds one) during a
10
+ * Vite/Astro (EmDash) build, and hot-regenerates when the config changes in dev.
11
11
  *
12
12
  * import vitops from '@getvitops/vite';
13
13
  * export default { plugins: [vitops({ input: 'design-system.json', format: 'tailwind', out: 'src/styles' })] };
@@ -17,6 +17,40 @@ import { generateFavicons, writeFaviconManifest } from "@getvitops/utils";
17
17
  /** Endpoint `<wc-theme-editor>` probes for, and POSTs its patch to. */
18
18
  const EDITOR_ENDPOINT = "/__vitops/design-system";
19
19
  /**
20
+ * Where the design system lives inside the config file on disk.
21
+ *
22
+ * `[]` for a `design-system.json` — the file *is* the design system. For a site
23
+ * config it is under `designSystem`, and this reads the **raw** object rather
24
+ * than the resolved one because the editor's write-back has to land where the
25
+ * author actually put it. `resolveSiteConfig` normalises the two shorthands in
26
+ * memory; a writer that assumed the canonical shape would grow a second
27
+ * `themes` key beside the author's bare map and silently stop editing anything.
28
+ */
29
+ function designSystemPath(raw, theme = "default") {
30
+ if (!isSiteConfig(raw)) return [];
31
+ const ds = raw.designSystem;
32
+ if (ds != null && typeof ds === "object") {
33
+ if ("themes" in ds) return [
34
+ "designSystem",
35
+ "themes",
36
+ theme
37
+ ];
38
+ if ("colors" in ds) return ["designSystem"];
39
+ }
40
+ return ["designSystem", theme];
41
+ }
42
+ const getAt = (obj, path) => path.reduce((cur, k) => cur?.[k], obj);
43
+ /** Return a copy of `obj` with `path` replaced by `value`. Creates missing objects. */
44
+ function setAt(obj, path, value) {
45
+ if (!path.length) return value;
46
+ const [head, ...rest] = path;
47
+ const base = typeof obj === "object" && obj !== null ? obj : {};
48
+ return {
49
+ ...base,
50
+ [head]: setAt(base[head], rest, value)
51
+ };
52
+ }
53
+ /**
20
54
  * Recursively merge a patch into a config. Plain objects merge key-wise;
21
55
  * everything else (scalars, and arrays — a token list is replaced, not appended)
22
56
  * is overwritten by the patch.
@@ -64,17 +98,27 @@ function vitops(options = {}) {
64
98
  const format = options.format ?? "tailwind";
65
99
  const outDir = options.out ?? "src/styles";
66
100
  let input = resolve(options.input ?? "design-system.json");
67
- let legalInput = options.legal ? resolve(options.legal.input) : "";
101
+ let legalInput = options.legal?.input ? resolve(options.legal.input) : "";
102
+ let siteInput = options.site ? resolve(options.site.input) : "";
68
103
  let root = "";
104
+ const readJson = (p) => JSON.parse(readFileSync(p, "utf8"));
69
105
  const run = async () => {
106
+ const raw = readJson(input);
107
+ const inputIsSite = isSiteConfig(raw);
70
108
  await generate({
71
- input,
109
+ input: raw,
72
110
  format,
73
- outDir
111
+ outDir,
112
+ ...options.theme != null ? { theme: options.theme } : {},
113
+ siteEnv: options.site?.siteEnv ?? "production",
114
+ ...options.systemColorScheme != null ? { systemColorScheme: options.systemColorScheme } : {},
115
+ ...siteInput ? { site: readJson(siteInput) } : {}
74
116
  });
75
117
  if (options.legal) {
118
+ const legalPath = legalInput || siteInput || (inputIsSite ? input : "");
119
+ if (!legalPath) throw new Error("[vitops] legal: needs a site config — set `legal.input`, or the `site` option, or point `input` at a site config.");
76
120
  const legalOut = resolve(root, options.legal.out ?? "src/content/legal");
77
- const files = generateLegal(resolveSiteConfig(JSON.parse(readFileSync(legalInput, "utf8")), options.legal.siteEnv ?? "production"), { output: options.legal.format ?? "md" });
121
+ const files = generateLegal(resolveSiteConfig(readJson(legalPath), options.legal.siteEnv ?? "production"), { output: options.legal.format ?? "md" });
78
122
  mkdirSync(legalOut, { recursive: true });
79
123
  for (const [name, content] of Object.entries(files)) writeFileSync(join(legalOut, name), content);
80
124
  }
@@ -86,12 +130,21 @@ function vitops(options = {}) {
86
130
  copyFileSync(src, join(dest, "design-manifest.json"));
87
131
  }
88
132
  }
133
+ if (options.spriteDir) {
134
+ const src = resolve(root, outDir, "icons.svg");
135
+ if (existsSync(src)) {
136
+ const dest = resolve(root, options.spriteDir, "vitops");
137
+ mkdirSync(dest, { recursive: true });
138
+ copyFileSync(src, join(dest, "icons.svg"));
139
+ }
140
+ }
89
141
  if (options.favicon) {
90
142
  const faviconOut = resolve(root, options.favicon.out ?? "public");
91
143
  await generateFavicons({
92
144
  source: resolve(root, options.favicon.source),
93
145
  outputDir: faviconOut,
94
- ...options.favicon.lowResSource ? { lowResSource: resolve(root, options.favicon.lowResSource) } : {}
146
+ ...options.favicon.lowResSource ? { lowResSource: resolve(root, options.favicon.lowResSource) } : {},
147
+ ...options.favicon.backgroundColor ? { backgroundColor: options.favicon.backgroundColor } : {}
95
148
  });
96
149
  if (options.favicon.name && options.favicon.themeColor) await writeFaviconManifest(faviconOut, {
97
150
  name: options.favicon.name,
@@ -100,13 +153,18 @@ function vitops(options = {}) {
100
153
  });
101
154
  }
102
155
  };
103
- const watched = () => legalInput ? [input, legalInput] : [input];
156
+ const watched = () => [...new Set([
157
+ input,
158
+ siteInput,
159
+ legalInput
160
+ ].filter(Boolean))];
104
161
  return {
105
162
  name: "@getvitops/vite",
106
163
  configResolved(config) {
107
164
  root = config.root;
108
165
  input = resolve(config.root, options.input ?? "design-system.json");
109
- if (options.legal) legalInput = resolve(config.root, options.legal.input);
166
+ if (options.legal?.input) legalInput = resolve(config.root, options.legal.input);
167
+ if (options.site) siteInput = resolve(config.root, options.site.input);
110
168
  },
111
169
  async buildStart() {
112
170
  for (const f of watched()) this.addWatchFile(f);
@@ -129,10 +187,13 @@ function vitops(options = {}) {
129
187
  res.end(JSON.stringify(body));
130
188
  };
131
189
  if (req.method === "GET") try {
190
+ const current = readJson(input);
191
+ const at = designSystemPath(current, options.theme);
132
192
  return json(200, {
133
193
  writable: true,
134
194
  path: input,
135
- config: JSON.parse(readFileSync(input, "utf8"))
195
+ at,
196
+ config: getAt(current, at)
136
197
  });
137
198
  } catch (err) {
138
199
  return json(500, { error: err.message });
@@ -144,10 +205,12 @@ function vitops(options = {}) {
144
205
  try {
145
206
  const { patch } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
146
207
  if (patch == null || typeof patch !== "object") return json(400, { errors: ["body must be { patch: object }"] });
147
- const merged = deepMerge(JSON.parse(readFileSync(input, "utf8")), patch);
208
+ const current = readJson(input);
209
+ const at = designSystemPath(current, options.theme);
210
+ const merged = deepMerge(getAt(current, at), patch);
148
211
  const result = validate(merged);
149
212
  if (!result.ok) return json(400, { errors: result.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`) });
150
- writeFileSync(input, `${formatConfig(merged)}\n`);
213
+ writeFileSync(input, `${formatConfig(setAt(current, at, merged))}\n`);
151
214
  return json(200, {
152
215
  ok: true,
153
216
  warnings: result.warnings
@@ -162,5 +225,5 @@ function vitops(options = {}) {
162
225
  }
163
226
 
164
227
  //#endregion
165
- export { vitops as default };
228
+ export { vitops as default, designSystemPath, getAt, setAt };
166
229
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @getvitops/vite — a Vite plugin that generates Vitops design-system output from a\n * `design-system.json` during a Vite/Astro (EmDash) build, and hot-regenerates when\n * the config changes in dev.\n *\n * import vitops from '@getvitops/vite';\n * export default { plugins: [vitops({ input: 'design-system.json', format: 'tailwind', out: 'src/styles' })] };\n *\n * Thin wrapper over @getvitops/generator — the same generator the CLI uses.\n */\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { Plugin } from 'vite';\nimport {\n generate,\n generateLegal,\n resolveSiteConfig,\n validate,\n type Format,\n type LegalOutput,\n} from '@getvitops/generator';\nimport { generateFavicons, writeFaviconManifest } from '@getvitops/utils';\n\n/** Endpoint `<wc-theme-editor>` probes for, and POSTs its patch to. */\nconst EDITOR_ENDPOINT = '/__vitops/design-system';\n\n/**\n * Recursively merge a patch into a config. Plain objects merge key-wise;\n * everything else (scalars, and arrays — a token list is replaced, not appended)\n * is overwritten by the patch.\n */\nfunction deepMerge(base: unknown, patch: unknown): unknown {\n const isPlain = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n if (!isPlain(base) || !isPlain(patch)) return patch;\n const out: Record<string, unknown> = { ...base };\n for (const [k, v] of Object.entries(patch)) out[k] = deepMerge(base[k], v);\n return out;\n}\n\n/** Printed width the config is formatted to — matches the repo formatter's default. */\nconst PRINT_WIDTH = 100;\n\n/**\n * Serialize a config the way a formatter would: 2-space indent, but arrays of\n * scalars kept on one line when they fit.\n *\n * `JSON.stringify(v, null, 2)` explodes every array onto its own lines, so writing\n * back a two-key patch reflowed ~300 lines of `design-system.json` — a diff nobody\n * can review, for a change of two values. Matching the prevailing style keeps the\n * write surgical, which is the whole point of saving to source rather than copying\n * a patch by hand.\n */\nfunction formatConfig(value: unknown, col = 0, indent = 0): string {\n const pad = ' '.repeat(indent);\n if (Array.isArray(value)) {\n if (!value.length) return '[]';\n if (value.every((v) => v === null || typeof v !== 'object')) {\n const inline = `[${value.map((v) => JSON.stringify(v)).join(', ')}]`;\n if (col + inline.length <= PRINT_WIDTH) return inline;\n }\n const items = value.map((v) => `${pad} ${formatConfig(v, indent + 2, indent + 2)}`);\n return `[\\n${items.join(',\\n')}\\n${pad}]`;\n }\n if (value !== null && typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>);\n if (!entries.length) return '{}';\n const items = entries.map(([k, v]) => {\n const head = `${pad} ${JSON.stringify(k)}: `;\n return head + formatConfig(v, head.length, indent + 2);\n });\n return `{\\n${items.join(',\\n')}\\n${pad}}`;\n }\n return JSON.stringify(value) ?? 'null';\n}\n\nexport interface VitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n /** Optional simplified source for the 16px icon. */\n lowResSource?: string;\n /** Directory to write the favicon set into. Default: 'public'. */\n out?: string;\n /** App name — when set (with `themeColor`), also emit `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also drives `<meta name=\"theme-color\">`). */\n themeColor?: string;\n /** PWA background color. */\n backgroundColor?: string;\n}\n\nexport interface VitopsPluginOptions {\n /** Path to the design-system.json. Default: 'design-system.json'. */\n input?: string;\n /** Output target. Default: 'tailwind' (the EmDash/Astro case). */\n format?: Format;\n /** Directory to write generated output into. Default: 'src/styles'. */\n out?: string;\n /** Also generate a favicon set on build from this source image. */\n favicon?: VitopsFaviconOptions;\n /**\n * Directory to mirror `design-manifest.json` into as `vitops/design-manifest.json`\n * — the URL `<wc-theme-editor>` fetches by default. Set by the Astro integration's\n * `editor` option. Copied inside the generate pass rather than by the caller, so\n * it can't race the first generation and can't go stale on regeneration.\n */\n editorManifestDir?: string;\n /**\n * Also render the site's legal documents on build, and re-render when the site\n * config changes.\n *\n * Its own option rather than part of the design-system pass because it reads a\n * different config kind — a `SiteConfig`, not a `design-system.json`.\n */\n legal?: VitopsLegalOptions;\n}\n\nexport interface VitopsLegalOptions {\n /** Path to the site config (JSON). */\n input: string;\n /** Directory to write documents into. Default: 'src/content/legal'. */\n out?: string;\n /** Output format. Default: 'md', which suits an Astro content collection. */\n format?: LegalOutput;\n /** Environment whose A/B variant applies. Default: 'production'. */\n siteEnv?: string;\n}\n\nexport default function vitops(options: VitopsPluginOptions = {}): Plugin {\n const format: Format = options.format ?? 'tailwind';\n const outDir = options.out ?? 'src/styles';\n let input = resolve(options.input ?? 'design-system.json');\n let legalInput = options.legal ? resolve(options.legal.input) : '';\n let root = '';\n\n const run = async () => {\n await generate({ input, format, outDir });\n if (options.legal) {\n const legalOut = resolve(root, options.legal.out ?? 'src/content/legal');\n const site = resolveSiteConfig(\n JSON.parse(readFileSync(legalInput, 'utf8')),\n options.legal.siteEnv ?? 'production',\n );\n const files = generateLegal(site, { output: options.legal.format ?? 'md' });\n mkdirSync(legalOut, { recursive: true });\n for (const [name, content] of Object.entries(files))\n writeFileSync(join(legalOut, name), content);\n }\n if (options.editorManifestDir) {\n const src = resolve(root, outDir, 'design-manifest.json');\n if (existsSync(src)) {\n const dest = resolve(root, options.editorManifestDir, 'vitops');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'design-manifest.json'));\n }\n }\n if (options.favicon) {\n const faviconOut = resolve(root, options.favicon.out ?? 'public');\n await generateFavicons({\n source: resolve(root, options.favicon.source),\n outputDir: faviconOut,\n ...(options.favicon.lowResSource\n ? { lowResSource: resolve(root, options.favicon.lowResSource) }\n : {}),\n });\n if (options.favicon.name && options.favicon.themeColor) {\n await writeFaviconManifest(faviconOut, {\n name: options.favicon.name,\n themeColor: options.favicon.themeColor,\n ...(options.favicon.backgroundColor\n ? { backgroundColor: options.favicon.backgroundColor }\n : {}),\n });\n }\n }\n };\n\n // Both configs are inputs to `run()`, so both must trigger a re-run — a legal\n // document that only refreshes when the design system changes is worse than\n // one that never refreshes, because it looks current.\n const watched = () => (legalInput ? [input, legalInput] : [input]);\n\n return {\n name: '@getvitops/vite',\n // Resolve paths against Vite's project root once it's known.\n configResolved(config) {\n root = config.root;\n input = resolve(config.root, options.input ?? 'design-system.json');\n if (options.legal) legalInput = resolve(config.root, options.legal.input);\n },\n async buildStart() {\n for (const f of watched()) this.addWatchFile(f);\n await run();\n },\n async watchChange(id) {\n if (watched().includes(resolve(id))) await run();\n },\n configureServer(server) {\n for (const f of watched()) server.watcher.add(f);\n server.watcher.on('change', async (file) => {\n if (!watched().includes(resolve(file))) return;\n await run();\n server.ws.send({ type: 'full-reload' });\n });\n\n // Dev-only design-system write-back, for `<wc-theme-editor>`'s \"Save to\n // source\". Registered in configureServer only, so it exists in `vite dev`\n // and never in a build — on a static deploy the editor's probe 404s and the\n // button simply isn't rendered. The only writable path is `input`, already\n // resolved from config: a request supplies a patch, never a destination.\n server.middlewares.use(EDITOR_ENDPOINT, (req, res, next) => {\n const json = (status: number, body: unknown): void => {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json');\n res.end(JSON.stringify(body));\n };\n\n if (req.method === 'GET') {\n try {\n return json(200, {\n writable: true,\n path: input,\n config: JSON.parse(readFileSync(input, 'utf8')),\n });\n } catch (err) {\n return json(500, { error: (err as Error).message });\n }\n }\n\n if (req.method !== 'POST') return next();\n\n const chunks: Buffer[] = [];\n req.on('data', (c: Buffer) => chunks.push(c));\n req.on('end', () => {\n try {\n const { patch } = JSON.parse(Buffer.concat(chunks).toString('utf8')) as {\n patch?: unknown;\n };\n if (patch == null || typeof patch !== 'object')\n return json(400, { errors: ['body must be { patch: object }'] });\n\n const current = JSON.parse(readFileSync(input, 'utf8')) as unknown;\n const merged = deepMerge(current, patch);\n // Validate the *merged* result, not the patch: a patch is a partial\n // by design, so it can't be validated on its own, and writing an\n // invalid config would break the very dev server doing the writing.\n const result = validate(merged);\n if (!result.ok)\n return json(400, {\n errors: result.errors.map((e) => `${e.path.join('.') || '(root)'}: ${e.message}`),\n });\n\n writeFileSync(input, `${formatConfig(merged)}\\n`);\n // The watcher above picks the write up and regenerates + reloads.\n return json(200, { ok: true, warnings: result.warnings });\n } catch (err) {\n return json(400, { errors: [(err as Error).message] });\n }\n });\n });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @getvitops/vite — a Vite plugin that generates Vitops design-system output from a\n * `design-system.json` (or from the larger site config that embeds one) during a\n * Vite/Astro (EmDash) build, and hot-regenerates when the config changes in dev.\n *\n * import vitops from '@getvitops/vite';\n * export default { plugins: [vitops({ input: 'design-system.json', format: 'tailwind', out: 'src/styles' })] };\n *\n * Thin wrapper over @getvitops/generator — the same generator the CLI uses.\n */\nimport { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { Plugin } from 'vite';\nimport {\n generate,\n generateLegal,\n isSiteConfig,\n resolveSiteConfig,\n validate,\n type Format,\n type LegalOutput,\n} from '@getvitops/generator';\nimport { generateFavicons, writeFaviconManifest } from '@getvitops/utils';\n\n/** Endpoint `<wc-theme-editor>` probes for, and POSTs its patch to. */\nconst EDITOR_ENDPOINT = '/__vitops/design-system';\n\n/**\n * Where the design system lives inside the config file on disk.\n *\n * `[]` for a `design-system.json` — the file *is* the design system. For a site\n * config it is under `designSystem`, and this reads the **raw** object rather\n * than the resolved one because the editor's write-back has to land where the\n * author actually put it. `resolveSiteConfig` normalises the two shorthands in\n * memory; a writer that assumed the canonical shape would grow a second\n * `themes` key beside the author's bare map and silently stop editing anything.\n */\nexport function designSystemPath(raw: unknown, theme = 'default'): string[] {\n if (!isSiteConfig(raw)) return [];\n const ds = (raw as Record<string, unknown>).designSystem as Record<string, unknown>;\n if (ds != null && typeof ds === 'object') {\n if ('themes' in ds) return ['designSystem', 'themes', theme];\n // A bare `DesignSystem` written inline — one theme, and it is `default`.\n if ('colors' in ds) return ['designSystem'];\n }\n return ['designSystem', theme]; // the legacy bare theme map\n}\n\nexport const getAt = (obj: unknown, path: string[]): unknown =>\n path.reduce<unknown>((cur, k) => (cur as Record<string, unknown>)?.[k], obj);\n\n/** Return a copy of `obj` with `path` replaced by `value`. Creates missing objects. */\nexport function setAt(obj: unknown, path: string[], value: unknown): unknown {\n if (!path.length) return value;\n const [head, ...rest] = path as [string, ...string[]];\n const base = (typeof obj === 'object' && obj !== null ? obj : {}) as Record<string, unknown>;\n return { ...base, [head]: setAt(base[head], rest, value) };\n}\n\n/**\n * Recursively merge a patch into a config. Plain objects merge key-wise;\n * everything else (scalars, and arrays — a token list is replaced, not appended)\n * is overwritten by the patch.\n */\nfunction deepMerge(base: unknown, patch: unknown): unknown {\n const isPlain = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n if (!isPlain(base) || !isPlain(patch)) return patch;\n const out: Record<string, unknown> = { ...base };\n for (const [k, v] of Object.entries(patch)) out[k] = deepMerge(base[k], v);\n return out;\n}\n\n/** Printed width the config is formatted to — matches the repo formatter's default. */\nconst PRINT_WIDTH = 100;\n\n/**\n * Serialize a config the way a formatter would: 2-space indent, but arrays of\n * scalars kept on one line when they fit.\n *\n * `JSON.stringify(v, null, 2)` explodes every array onto its own lines, so writing\n * back a two-key patch reflowed ~300 lines of `design-system.json` — a diff nobody\n * can review, for a change of two values. Matching the prevailing style keeps the\n * write surgical, which is the whole point of saving to source rather than copying\n * a patch by hand.\n */\nfunction formatConfig(value: unknown, col = 0, indent = 0): string {\n const pad = ' '.repeat(indent);\n if (Array.isArray(value)) {\n if (!value.length) return '[]';\n if (value.every((v) => v === null || typeof v !== 'object')) {\n const inline = `[${value.map((v) => JSON.stringify(v)).join(', ')}]`;\n if (col + inline.length <= PRINT_WIDTH) return inline;\n }\n const items = value.map((v) => `${pad} ${formatConfig(v, indent + 2, indent + 2)}`);\n return `[\\n${items.join(',\\n')}\\n${pad}]`;\n }\n if (value !== null && typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>);\n if (!entries.length) return '{}';\n const items = entries.map(([k, v]) => {\n const head = `${pad} ${JSON.stringify(k)}: `;\n return head + formatConfig(v, head.length, indent + 2);\n });\n return `{\\n${items.join(',\\n')}\\n${pad}}`;\n }\n return JSON.stringify(value) ?? 'null';\n}\n\nexport interface VitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n /** Optional simplified source for the 16px icon. */\n lowResSource?: string;\n /** Directory to write the favicon set into. Default: 'public'. */\n out?: string;\n /** App name — when set (with `themeColor`), also emit `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also drives `<meta name=\"theme-color\">`). */\n themeColor?: string;\n /** PWA background color. */\n backgroundColor?: string;\n}\n\nexport interface VitopsPluginOptions {\n /**\n * Path to the config. Default: `'design-system.json'`.\n *\n * May be a `design-system.json` **or** the larger site config that embeds one\n * (`company.json` / `site.json`) — told apart by shape. Pointing it at a site\n * config also supplies `site` below, so a consumer who keeps their tokens\n * there declares the path once instead of three times.\n */\n input?: string;\n /**\n * Which `designSystem.themes` entry to build, when `input` is a site config.\n * Default: the config's `defaultTheme`, else `default`.\n *\n * Also the theme `<wc-theme-editor>`'s **Save to source** writes back into.\n */\n theme?: string;\n /** Output target. Default: 'tailwind' (the EmDash/Astro case). */\n format?: Format;\n /** Directory to write generated output into. Default: 'src/styles'. */\n out?: string;\n /** Also generate a favicon set on build from this source image. */\n favicon?: VitopsFaviconOptions;\n /**\n * Directory to mirror `design-manifest.json` into as `vitops/design-manifest.json`\n * — the URL `<wc-theme-editor>` fetches by default. Set by the Astro integration's\n * `editor` option. Copied inside the generate pass rather than by the caller, so\n * it can't race the first generation and can't go stale on regeneration.\n */\n editorManifestDir?: string;\n /**\n * Directory to mirror the generated `icons.svg` into as `vitops/icons.svg` —\n * the URL `<Icon />` targets under the `sprite` engine. Same reasoning as\n * `editorManifestDir`: copied inside the generate pass, so it can't race a\n * cold start or go stale after a config edit. The *href* is a plain string\n * decided at config:setup; only the bytes arrive here.\n */\n spriteDir?: string;\n /**\n * Also render the site's legal documents on build, and re-render when the site\n * config changes.\n *\n * Its own option because it is an *output* you opt into — but its `input` is\n * optional, defaulting to `site.input`, then to `input` when that is itself a\n * site config. `legal: {}` is therefore the whole declaration in the common case.\n */\n legal?: VitopsLegalOptions;\n /**\n * The site config, for the parts of generation that depend on site-level facts\n * rather than on the design system — currently `designSystem.defaultColorScheme`, which\n * decides whether the colour layer carries a `prefers-color-scheme` block.\n *\n * Only needed when the site config is a *different* file from `input`; when\n * `input` is itself a site config, that one is used. Watched, so an edit\n * regenerates.\n */\n site?: VitopsSiteOptions;\n /**\n * Emit the `prefers-color-scheme: dark` block. Normally read from the site\n * config's `designSystem.defaultColorScheme: \"system\"`; set here it wins, for consumers with no\n * site config.\n */\n systemColorScheme?: boolean;\n}\n\nexport interface VitopsSiteOptions {\n /** Path to the site config (JSON). */\n input: string;\n /** Environment whose A/B variant applies. Default: 'production'. */\n siteEnv?: string;\n}\n\nexport interface VitopsLegalOptions {\n /**\n * Path to the site config (JSON). Optional: defaults to `site.input`, then to\n * the plugin's own `input` when that is itself a site config.\n */\n input?: string;\n /** Directory to write documents into. Default: 'src/content/legal'. */\n out?: string;\n /** Output format. Default: 'md', which suits an Astro content collection. */\n format?: LegalOutput;\n /** Environment whose A/B variant applies. Default: 'production'. */\n siteEnv?: string;\n}\n\nexport default function vitops(options: VitopsPluginOptions = {}): Plugin {\n const format: Format = options.format ?? 'tailwind';\n const outDir = options.out ?? 'src/styles';\n let input = resolve(options.input ?? 'design-system.json');\n let legalInput = options.legal?.input ? resolve(options.legal.input) : '';\n let siteInput = options.site ? resolve(options.site.input) : '';\n let root = '';\n\n const readJson = (p: string) => JSON.parse(readFileSync(p, 'utf8')) as unknown;\n\n const run = async () => {\n // Read once and hand `generate` the parsed object: it has to be inspected\n // here anyway to know whether `input` is a site config, and reading the same\n // file twice per regeneration is how a half-written save gets picked up.\n const raw = readJson(input);\n const inputIsSite = isSiteConfig(raw);\n // `generate` reads the site config only for what the stylesheet depends on;\n // legal documents stay the separate opt-in below.\n await generate({\n input: raw as Parameters<typeof generate>[0]['input'],\n format,\n outDir,\n ...(options.theme != null ? { theme: options.theme } : {}),\n siteEnv: options.site?.siteEnv ?? 'production',\n ...(options.systemColorScheme != null\n ? { systemColorScheme: options.systemColorScheme }\n : {}),\n // An explicit `site` wins; otherwise `input` supplies it when it is one.\n ...(siteInput ? { site: readJson(siteInput) as never } : {}),\n });\n if (options.legal) {\n // The three paths are the same file in almost every project, so the\n // declaration cascades rather than being repeated.\n const legalPath = legalInput || siteInput || (inputIsSite ? input : '');\n if (!legalPath)\n throw new Error(\n '[vitops] legal: needs a site config — set `legal.input`, or the `site` option, or ' +\n 'point `input` at a site config.',\n );\n const legalOut = resolve(root, options.legal.out ?? 'src/content/legal');\n const site = resolveSiteConfig(readJson(legalPath), options.legal.siteEnv ?? 'production');\n const files = generateLegal(site, { output: options.legal.format ?? 'md' });\n mkdirSync(legalOut, { recursive: true });\n for (const [name, content] of Object.entries(files))\n writeFileSync(join(legalOut, name), content);\n }\n if (options.editorManifestDir) {\n const src = resolve(root, outDir, 'design-manifest.json');\n if (existsSync(src)) {\n const dest = resolve(root, options.editorManifestDir, 'vitops');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'design-manifest.json'));\n }\n }\n if (options.spriteDir) {\n const src = resolve(root, outDir, 'icons.svg');\n if (existsSync(src)) {\n const dest = resolve(root, options.spriteDir, 'vitops');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'icons.svg'));\n }\n }\n if (options.favicon) {\n const faviconOut = resolve(root, options.favicon.out ?? 'public');\n await generateFavicons({\n source: resolve(root, options.favicon.source),\n outputDir: faviconOut,\n ...(options.favicon.lowResSource\n ? { lowResSource: resolve(root, options.favicon.lowResSource) }\n : {}),\n // Composited under the maskable outputs, so they match the manifest's\n // `background_color` rather than being transparent where it must not be.\n ...(options.favicon.backgroundColor\n ? { backgroundColor: options.favicon.backgroundColor }\n : {}),\n });\n if (options.favicon.name && options.favicon.themeColor) {\n await writeFaviconManifest(faviconOut, {\n name: options.favicon.name,\n themeColor: options.favicon.themeColor,\n ...(options.favicon.backgroundColor\n ? { backgroundColor: options.favicon.backgroundColor }\n : {}),\n });\n }\n }\n };\n\n // Every config `run()` reads is an input, so every one must trigger a re-run —\n // a legal document (or a colour layer) that only refreshes when the design\n // system changes is worse than one that never refreshes, because it looks\n // current. De-duplicated because `site.input` and `legal.input` are usually the\n // same file, and watching it twice would regenerate twice per edit.\n const watched = () => [...new Set([input, siteInput, legalInput].filter(Boolean))];\n\n return {\n name: '@getvitops/vite',\n // Resolve paths against Vite's project root once it's known.\n configResolved(config) {\n root = config.root;\n input = resolve(config.root, options.input ?? 'design-system.json');\n if (options.legal?.input) legalInput = resolve(config.root, options.legal.input);\n if (options.site) siteInput = resolve(config.root, options.site.input);\n },\n async buildStart() {\n for (const f of watched()) this.addWatchFile(f);\n await run();\n },\n async watchChange(id) {\n if (watched().includes(resolve(id))) await run();\n },\n configureServer(server) {\n for (const f of watched()) server.watcher.add(f);\n server.watcher.on('change', async (file) => {\n if (!watched().includes(resolve(file))) return;\n await run();\n server.ws.send({ type: 'full-reload' });\n });\n\n // Dev-only design-system write-back, for `<wc-theme-editor>`'s \"Save to\n // source\". Registered in configureServer only, so it exists in `vite dev`\n // and never in a build — on a static deploy the editor's probe 404s and the\n // button simply isn't rendered. The only writable path is `input`, already\n // resolved from config: a request supplies a patch, never a destination.\n //\n // The editor's patch is always design-system-relative (`colors.palette.…`),\n // because that is the only config it knows about. When `input` is a site\n // config the patch is therefore merged into — and validated against — the\n // design system SUBTREE, and only the surrounding file is written whole.\n // Merging it at the root instead would write a `colors` key beside\n // `organization` that nothing reads, and `validate` would reject the site\n // config wholesale, so every save would fail with errors about the wrong\n // document.\n server.middlewares.use(EDITOR_ENDPOINT, (req, res, next) => {\n const json = (status: number, body: unknown): void => {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json');\n res.end(JSON.stringify(body));\n };\n\n if (req.method === 'GET') {\n try {\n const current = readJson(input);\n const at = designSystemPath(current, options.theme);\n return json(200, {\n writable: true,\n path: input,\n // The subtree, not the file: the editor's own model of a config is\n // a design system, and `at` says where in the file it lives.\n at,\n config: getAt(current, at),\n });\n } catch (err) {\n return json(500, { error: (err as Error).message });\n }\n }\n\n if (req.method !== 'POST') return next();\n\n const chunks: Buffer[] = [];\n req.on('data', (c: Buffer) => chunks.push(c));\n req.on('end', () => {\n try {\n const { patch } = JSON.parse(Buffer.concat(chunks).toString('utf8')) as {\n patch?: unknown;\n };\n if (patch == null || typeof patch !== 'object')\n return json(400, { errors: ['body must be { patch: object }'] });\n\n const current = readJson(input);\n const at = designSystemPath(current, options.theme);\n const merged = deepMerge(getAt(current, at), patch);\n // Validate the *merged* result, not the patch: a patch is a partial\n // by design, so it can't be validated on its own, and writing an\n // invalid config would break the very dev server doing the writing.\n const result = validate(merged);\n if (!result.ok)\n return json(400, {\n errors: result.errors.map((e) => `${e.path.join('.') || '(root)'}: ${e.message}`),\n });\n\n writeFileSync(input, `${formatConfig(setAt(current, at, merged))}\\n`);\n // The watcher above picks the write up and regenerates + reloads.\n return json(200, { ok: true, warnings: result.warnings });\n } catch (err) {\n return json(400, { errors: [(err as Error).message] });\n }\n });\n });\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/vite",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Vite plugin that generates Vitops design-system outputs from a design-system.json during a Vite/Astro build.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -18,11 +18,12 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@getvitops/generator": "1.0.0",
22
- "@getvitops/utils": "1.0.0"
21
+ "@getvitops/utils": "2.0.0",
22
+ "@getvitops/generator": "2.0.0"
23
23
  },
24
24
  "devDependencies": {
25
- "vite": "^6.0.0"
25
+ "vite": "^6.0.0",
26
+ "vitest": "^4.1.9"
26
27
  },
27
28
  "peerDependencies": {
28
29
  "vite": ">=5"