@getvitops/vite 8.0.1 → 8.2.0-alpha.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 +157 -8
- package/dist/index.d.mts +8 -23
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +8 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,148 @@
|
|
|
1
1
|
# @getvitops/vite
|
|
2
2
|
|
|
3
|
+
## 8.2.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
8
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
9
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
10
|
+
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
11
|
+
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
12
|
+
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
13
|
+
`page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
|
|
14
|
+
secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
|
|
15
|
+
reaches the git-tracked `site.json` through your repo's normal review path rather than
|
|
16
|
+
becoming a second, unreviewed source of truth. Until that's configured, the live half still
|
|
17
|
+
works; only the auto-commit half is skipped (and says so in the admin page). The save route
|
|
18
|
+
runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
|
|
19
|
+
synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
|
|
20
|
+
|
|
21
|
+
Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
|
|
22
|
+
EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
|
|
23
|
+
Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
|
|
24
|
+
auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
|
|
25
|
+
plugin-install time.
|
|
26
|
+
|
|
27
|
+
**Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
|
|
28
|
+
capability is now also granted whenever `designSystemEditor` is set, independent of the
|
|
29
|
+
existing `scripts: 'fragments'` option (which governs something unrelated — web-component
|
|
30
|
+
script delivery). No change if you don't use `designSystemEditor`.
|
|
31
|
+
|
|
32
|
+
Also new, supporting the above:
|
|
33
|
+
- `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
|
|
34
|
+
`/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
|
|
35
|
+
`design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
|
|
36
|
+
standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
|
|
37
|
+
into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
|
|
38
|
+
`mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
|
|
39
|
+
(re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
|
|
40
|
+
subtree" implementation shared by the dev endpoint and the new plugin route. New
|
|
41
|
+
`site.deployment.repo`/`branch` config fields.
|
|
42
|
+
- `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
|
|
43
|
+
wrapper over GitHub's Contents API.
|
|
44
|
+
- `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
|
|
45
|
+
unchanged — `/__vitops/design-system`) so the same element can target the new plugin
|
|
46
|
+
route instead of the dev-only Vite endpoint.
|
|
47
|
+
|
|
48
|
+
- 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
|
|
49
|
+
both projects' GA releases.
|
|
50
|
+
|
|
51
|
+
**Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
|
|
52
|
+
`emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
|
|
53
|
+
`^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
|
|
54
|
+
the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
|
|
55
|
+
code here needed to change, only the declared range.
|
|
56
|
+
|
|
57
|
+
The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
|
|
58
|
+
the version bump:
|
|
59
|
+
- `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
|
|
60
|
+
`css.format: 'tailwind'` without installing either, which threw on build.
|
|
61
|
+
- `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
|
|
62
|
+
`vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
|
|
63
|
+
the build never generated.
|
|
64
|
+
- `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
|
|
65
|
+
`site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
|
|
66
|
+
migration sequence explicitly, rather than relying solely on first-request auto-migration. This
|
|
67
|
+
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
68
|
+
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
69
|
+
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
70
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
71
|
+
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
72
|
+
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
73
|
+
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
74
|
+
Uncomment it once scaffolded into its own repo.
|
|
75
|
+
|
|
76
|
+
No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
|
|
77
|
+
to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
|
|
78
|
+
losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
|
|
79
|
+
now names the bad key instead of reporting a bare "Invalid input").
|
|
80
|
+
|
|
81
|
+
Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
|
|
82
|
+
(`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
|
|
83
|
+
has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
|
|
84
|
+
by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
|
|
85
|
+
hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
|
|
86
|
+
`postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
|
|
87
|
+
`node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
|
|
88
|
+
hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
|
|
89
|
+
(the same path `astro sync` would generate for real). Both are gitignored and only ever exist
|
|
90
|
+
inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
|
|
91
|
+
Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
|
|
92
|
+
evolving default) and making the template a real pnpm workspace member (would change how its
|
|
93
|
+
`@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
|
|
94
|
+
|
|
95
|
+
**Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
|
|
96
|
+
`AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
|
|
97
|
+
`.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
|
|
98
|
+
repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
|
|
99
|
+
compose with vite-plus's own `defineConfig`). A type-level shim
|
|
100
|
+
(`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
|
|
101
|
+
runtime `vp test` failure for this one file is still open.
|
|
102
|
+
|
|
103
|
+
- Updated dependencies [65a13cc]
|
|
104
|
+
- Updated dependencies [67e2088]
|
|
105
|
+
- Updated dependencies [0574388]
|
|
106
|
+
- Updated dependencies [c3c1bcb]
|
|
107
|
+
- Updated dependencies [0574388]
|
|
108
|
+
- Updated dependencies [0574388]
|
|
109
|
+
- Updated dependencies [0574388]
|
|
110
|
+
- Updated dependencies [1a3e811]
|
|
111
|
+
- Updated dependencies [0574388]
|
|
112
|
+
- Updated dependencies [dde3df3]
|
|
113
|
+
- Updated dependencies [d9f213b]
|
|
114
|
+
- @getvitops/generator@8.2.0-alpha.0
|
|
115
|
+
- @getvitops/utils@8.2.0-alpha.0
|
|
116
|
+
|
|
117
|
+
## 8.1.0
|
|
118
|
+
|
|
119
|
+
### Patch Changes
|
|
120
|
+
|
|
121
|
+
- **Added — `vitops init --site` and `vitops setup`, the scripted client-onboarding path.**
|
|
122
|
+
|
|
123
|
+
`vitops init --site [--name] [--url]` scaffolds a starter `site.json` (the umbrella `Config`,
|
|
124
|
+
embedding a starter design system at `designSystem.themes.default`) instead of a bare
|
|
125
|
+
`design-system.json` — exported as `defaultSiteConfig()` from `@getvitops/generator`, alongside a
|
|
126
|
+
new `CONFIG_SCHEMA_LOCAL_PATH` and an optional `$schema` field on `ConfigSchema` so a scaffolded
|
|
127
|
+
config gets editor autocomplete the same way `design-system.json` already does.
|
|
128
|
+
|
|
129
|
+
`vitops setup <repo|hosting|domain|search|ads|all>` (new `@getvitops/utils/hosting` subpath: a
|
|
130
|
+
pure planner + `wrangler`/`gh`-shelling executors, following the same idempotent
|
|
131
|
+
plan/observe/apply split as `onboarding`/`ads`) automates GitHub repo + branch policy and
|
|
132
|
+
Cloudflare dev D1/R2 + repo secrets, and plans/checks the workers.dev-vs-custom-domain
|
|
133
|
+
consistency without ever applying the cutover itself — see AGENTS.md's new "Client onboarding"
|
|
134
|
+
section for the full design rationale. `search`/`ads` forward to the existing `search setup`/
|
|
135
|
+
`ads setup` subcommands rather than reimplementing them.
|
|
136
|
+
|
|
137
|
+
No consumer-facing change in `@getvitops/vite` or `@getvitops/astro` in this release — bumped
|
|
138
|
+
only because they are in the `fixed` release group with `generator`/`utils`/`cli`.
|
|
139
|
+
`@getvitops/core` does have a real fix in this release; see the separate `banner-hidden-fix`
|
|
140
|
+
changeset.
|
|
141
|
+
|
|
142
|
+
- Updated dependencies
|
|
143
|
+
- @getvitops/generator@8.1.0
|
|
144
|
+
- @getvitops/utils@8.1.0
|
|
145
|
+
|
|
3
146
|
## 8.0.1
|
|
4
147
|
|
|
5
148
|
### Patch Changes
|
|
@@ -16,7 +159,6 @@
|
|
|
16
159
|
|
|
17
160
|
**Breaking (`css`/`bricks` and `tailwind` formats — `.lightbox-dialog`'s markup
|
|
18
161
|
contract changed):**
|
|
19
|
-
|
|
20
162
|
- `<Gallery />` now emits ONE `<dialog class="lightbox-dialog">` per gallery,
|
|
21
163
|
wrapping a `<Carousel />`, instead of one dialog per image. A `.gallery__trigger`
|
|
22
164
|
is now a plain `<a href="#slide-id">`, not a `command="show-modal"` button.
|
|
@@ -65,7 +207,6 @@
|
|
|
65
207
|
`--site` for now.)
|
|
66
208
|
|
|
67
209
|
**Fixed:**
|
|
68
|
-
|
|
69
210
|
- `@getvitops/core`'s 12 runtime deps (`lit`, both `@oddbird` polyfills, both
|
|
70
211
|
`@webcomponents` packages, `colorjs.io`, etc.) were listed as `dependencies` despite
|
|
71
212
|
being bundled into `dist/elements.js`/`dist/polyfills.js`/`dist/deferred.js` — a
|
|
@@ -317,20 +458,28 @@ options, watch behaviour and dev-server endpoints are unchanged.
|
|
|
317
458
|
{
|
|
318
459
|
"designSystem": {
|
|
319
460
|
"themes": {
|
|
320
|
-
"default": {
|
|
461
|
+
"default": {
|
|
462
|
+
/* … */
|
|
463
|
+
},
|
|
321
464
|
},
|
|
322
465
|
},
|
|
323
466
|
"organization": {
|
|
324
467
|
"name": "Acme",
|
|
325
468
|
"contact": "hq",
|
|
326
469
|
"locations": {
|
|
327
|
-
"hq": {
|
|
470
|
+
"hq": {
|
|
471
|
+
/* … */
|
|
472
|
+
},
|
|
328
473
|
},
|
|
329
474
|
},
|
|
330
475
|
"site": {
|
|
331
476
|
"defaultLocale": "en",
|
|
332
|
-
"domains": {
|
|
333
|
-
|
|
477
|
+
"domains": {
|
|
478
|
+
/* … */
|
|
479
|
+
},
|
|
480
|
+
"analytics": {
|
|
481
|
+
/* … */
|
|
482
|
+
},
|
|
334
483
|
},
|
|
335
484
|
}
|
|
336
485
|
```
|
|
@@ -438,7 +587,7 @@ ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails
|
|
|
438
587
|
|
|
439
588
|
```js
|
|
440
589
|
// astro.config.mjs
|
|
441
|
-
getvitops({ css: { input:
|
|
590
|
+
getvitops({ css: { input: 'company.json', format: 'css' } });
|
|
442
591
|
```
|
|
443
592
|
|
|
444
593
|
```
|
|
@@ -749,7 +898,7 @@ ffmpeg`, `apt install ffmpeg`, `winget install Gyan.FFmpeg`). The command fails
|
|
|
749
898
|
```js
|
|
750
899
|
// astro.config.mjs
|
|
751
900
|
getvitops({
|
|
752
|
-
css: { input:
|
|
901
|
+
css: { input: 'design-system.json', format: 'css', out: 'src/styles' },
|
|
753
902
|
editor: true,
|
|
754
903
|
});
|
|
755
904
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
import { Format, LegalOutput } from "@getvitops/generator";
|
|
1
|
+
import { Format, LegalOutput, designSystemPath, getAt, setAt } from "@getvitops/generator";
|
|
2
2
|
import { OutputKind } from "@getvitops/utils/media";
|
|
3
3
|
import { Plugin } from "vite";
|
|
4
|
-
|
|
5
4
|
//#region src/index.d.ts
|
|
6
|
-
|
|
7
|
-
* Where the design system lives inside the config file on disk.
|
|
8
|
-
*
|
|
9
|
-
* `[]` for a `design-system.json` — the file *is* the design system. For a site
|
|
10
|
-
* config it is under `designSystem`, and this reads the **raw** object rather
|
|
11
|
-
* than the resolved one because the editor's write-back has to land where the
|
|
12
|
-
* author actually put it. `resolveConfig` normalises the two shorthands in
|
|
13
|
-
* memory; a writer that assumed the canonical shape would grow a second
|
|
14
|
-
* `themes` key beside the author's bare map and silently stop editing anything.
|
|
15
|
-
*/
|
|
16
|
-
declare function designSystemPath(raw: unknown, theme?: string): string[];
|
|
17
|
-
declare const getAt: (obj: unknown, path: string[]) => unknown;
|
|
18
|
-
/** Return a copy of `obj` with `path` replaced by `value`. Creates missing objects. */
|
|
19
|
-
declare function setAt(obj: unknown, path: string[], value: unknown): unknown;
|
|
20
|
-
interface VitopsMediaOptions {
|
|
5
|
+
export interface VitopsMediaOptions {
|
|
21
6
|
/** Directory of unprocessed video, walked recursively. Default: 'raw'. */
|
|
22
7
|
raw?: string;
|
|
23
8
|
/**
|
|
@@ -42,7 +27,7 @@ interface VitopsMediaOptions {
|
|
|
42
27
|
/** Cache file. Default `.vitops/media-manifest.json`. */
|
|
43
28
|
manifest?: string;
|
|
44
29
|
}
|
|
45
|
-
interface VitopsFaviconOptions {
|
|
30
|
+
export interface VitopsFaviconOptions {
|
|
46
31
|
/** Source SVG or PNG. */
|
|
47
32
|
source: string;
|
|
48
33
|
/** Optional simplified source for the 16px icon. */
|
|
@@ -56,7 +41,7 @@ interface VitopsFaviconOptions {
|
|
|
56
41
|
/** PWA background color. */
|
|
57
42
|
backgroundColor?: string;
|
|
58
43
|
}
|
|
59
|
-
interface VitopsPluginOptions {
|
|
44
|
+
export interface VitopsPluginOptions {
|
|
60
45
|
/**
|
|
61
46
|
* Path to the config. Default: `'design-system.json'`.
|
|
62
47
|
*
|
|
@@ -137,13 +122,13 @@ interface VitopsPluginOptions {
|
|
|
137
122
|
*/
|
|
138
123
|
systemColorScheme?: boolean;
|
|
139
124
|
}
|
|
140
|
-
interface VitopsSiteOptions {
|
|
125
|
+
export interface VitopsSiteOptions {
|
|
141
126
|
/** Path to the site config (JSON). */
|
|
142
127
|
input: string;
|
|
143
128
|
/** Environment whose A/B variant applies. Default: 'production'. */
|
|
144
129
|
siteEnv?: string;
|
|
145
130
|
}
|
|
146
|
-
interface VitopsLegalOptions {
|
|
131
|
+
export interface VitopsLegalOptions {
|
|
147
132
|
/**
|
|
148
133
|
* Path to the site config (JSON). Optional: defaults to `site.input`, then to
|
|
149
134
|
* the plugin's own `input` when that is itself a site config.
|
|
@@ -156,7 +141,7 @@ interface VitopsLegalOptions {
|
|
|
156
141
|
/** Environment whose A/B variant applies. Default: 'production'. */
|
|
157
142
|
siteEnv?: string;
|
|
158
143
|
}
|
|
159
|
-
|
|
144
|
+
export default function vitops(options?: VitopsPluginOptions): Plugin;
|
|
160
145
|
//#endregion
|
|
161
|
-
export {
|
|
146
|
+
export { designSystemPath, getAt, setAt };
|
|
162
147
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;iBA6CiB;;EAEf;;;;;;;EAOA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,UAAU;;EAEV;;iBAGe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;iBAGe;;;;;;;;;EASf;;;;;;;EAOA;;EAEA,SAAS;;EAET;;EAEA,UAAU;;;;;;;;;;;;;;;;;EAiBV,QAAQ;;;;;;;EAOR;;;;;;;;EAQA;;;;;;;;;EASA,QAAQ;;;;;;;;;;EAUR,OAAO;;;;;;EAMP;;iBAGe;;EAEf;;EAEA;;iBAGe;;;;;EAKf;;EAEA;;EAEA,SAAS;;EAET;;wBAGsB,OAAO,UAAS,sBAA2B"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import { generate, generateLegal, isConfig, resolveConfig, validate } from "@getvitops/generator";
|
|
3
|
+
import { designSystemPath, designSystemPath as designSystemPath$1, formatConfig, generate, generateLegal, getAt, getAt as getAt$1, isConfig, mergePatch, resolveConfig, setAt, setAt as setAt$1, validate } from "@getvitops/generator";
|
|
4
4
|
import { generateFavicons, writeFaviconManifest } from "@getvitops/utils";
|
|
5
5
|
import { MEDIA_MANIFEST_PATH, processMedia } from "@getvitops/utils/media";
|
|
6
6
|
|
|
@@ -17,84 +17,6 @@ import { MEDIA_MANIFEST_PATH, processMedia } from "@getvitops/utils/media";
|
|
|
17
17
|
*/
|
|
18
18
|
/** Endpoint `<wc-theme-editor>` probes for, and POSTs its patch to. */
|
|
19
19
|
const EDITOR_ENDPOINT = "/__vitops/design-system";
|
|
20
|
-
/**
|
|
21
|
-
* Where the design system lives inside the config file on disk.
|
|
22
|
-
*
|
|
23
|
-
* `[]` for a `design-system.json` — the file *is* the design system. For a site
|
|
24
|
-
* config it is under `designSystem`, and this reads the **raw** object rather
|
|
25
|
-
* than the resolved one because the editor's write-back has to land where the
|
|
26
|
-
* author actually put it. `resolveConfig` normalises the two shorthands in
|
|
27
|
-
* memory; a writer that assumed the canonical shape would grow a second
|
|
28
|
-
* `themes` key beside the author's bare map and silently stop editing anything.
|
|
29
|
-
*/
|
|
30
|
-
function designSystemPath(raw, theme = "default") {
|
|
31
|
-
if (!isConfig(raw)) return [];
|
|
32
|
-
const ds = raw.designSystem;
|
|
33
|
-
if (ds != null && typeof ds === "object") {
|
|
34
|
-
if ("themes" in ds) return [
|
|
35
|
-
"designSystem",
|
|
36
|
-
"themes",
|
|
37
|
-
theme
|
|
38
|
-
];
|
|
39
|
-
if ("colors" in ds) return ["designSystem"];
|
|
40
|
-
}
|
|
41
|
-
return ["designSystem", theme];
|
|
42
|
-
}
|
|
43
|
-
const getAt = (obj, path) => path.reduce((cur, k) => cur?.[k], obj);
|
|
44
|
-
/** Return a copy of `obj` with `path` replaced by `value`. Creates missing objects. */
|
|
45
|
-
function setAt(obj, path, value) {
|
|
46
|
-
if (!path.length) return value;
|
|
47
|
-
const [head, ...rest] = path;
|
|
48
|
-
const base = typeof obj === "object" && obj !== null ? obj : {};
|
|
49
|
-
return {
|
|
50
|
-
...base,
|
|
51
|
-
[head]: setAt(base[head], rest, value)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Recursively merge a patch into a config. Plain objects merge key-wise;
|
|
56
|
-
* everything else (scalars, and arrays — a token list is replaced, not appended)
|
|
57
|
-
* is overwritten by the patch.
|
|
58
|
-
*/
|
|
59
|
-
function deepMerge(base, patch) {
|
|
60
|
-
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
61
|
-
if (!isPlain(base) || !isPlain(patch)) return patch;
|
|
62
|
-
const out = { ...base };
|
|
63
|
-
for (const [k, v] of Object.entries(patch)) out[k] = deepMerge(base[k], v);
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
/** Printed width the config is formatted to — matches the repo formatter's default. */
|
|
67
|
-
const PRINT_WIDTH = 100;
|
|
68
|
-
/**
|
|
69
|
-
* Serialize a config the way a formatter would: 2-space indent, but arrays of
|
|
70
|
-
* scalars kept on one line when they fit.
|
|
71
|
-
*
|
|
72
|
-
* `JSON.stringify(v, null, 2)` explodes every array onto its own lines, so writing
|
|
73
|
-
* back a two-key patch reflowed ~300 lines of `design-system.json` — a diff nobody
|
|
74
|
-
* can review, for a change of two values. Matching the prevailing style keeps the
|
|
75
|
-
* write surgical, which is the whole point of saving to source rather than copying
|
|
76
|
-
* a patch by hand.
|
|
77
|
-
*/
|
|
78
|
-
function formatConfig(value, col = 0, indent = 0) {
|
|
79
|
-
const pad = " ".repeat(indent);
|
|
80
|
-
if (Array.isArray(value)) {
|
|
81
|
-
if (!value.length) return "[]";
|
|
82
|
-
if (value.every((v) => v === null || typeof v !== "object")) {
|
|
83
|
-
const inline = `[${value.map((v) => JSON.stringify(v)).join(", ")}]`;
|
|
84
|
-
if (col + inline.length <= PRINT_WIDTH) return inline;
|
|
85
|
-
}
|
|
86
|
-
return `[\n${value.map((v) => `${pad} ${formatConfig(v, indent + 2, indent + 2)}`).join(",\n")}\n${pad}]`;
|
|
87
|
-
}
|
|
88
|
-
if (value !== null && typeof value === "object") {
|
|
89
|
-
const entries = Object.entries(value);
|
|
90
|
-
if (!entries.length) return "{}";
|
|
91
|
-
return `{\n${entries.map(([k, v]) => {
|
|
92
|
-
const head = `${pad} ${JSON.stringify(k)}: `;
|
|
93
|
-
return head + formatConfig(v, head.length, indent + 2);
|
|
94
|
-
}).join(",\n")}\n${pad}}`;
|
|
95
|
-
}
|
|
96
|
-
return JSON.stringify(value) ?? "null";
|
|
97
|
-
}
|
|
98
20
|
function vitops(options = {}) {
|
|
99
21
|
const format = options.format ?? "tailwind";
|
|
100
22
|
const outDir = options.out ?? "src/styles";
|
|
@@ -119,7 +41,8 @@ function vitops(options = {}) {
|
|
|
119
41
|
const legalPath = legalInput || siteInput || (inputIsSite ? input : "");
|
|
120
42
|
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.");
|
|
121
43
|
const legalOut = resolve(root, options.legal.out ?? "src/content/legal");
|
|
122
|
-
const
|
|
44
|
+
const site = resolveConfig(readJson(legalPath), options.legal.siteEnv ?? "production");
|
|
45
|
+
const files = generateLegal(site, { output: options.legal.format ?? "md" });
|
|
123
46
|
mkdirSync(legalOut, { recursive: true });
|
|
124
47
|
for (const [name, content] of Object.entries(files)) writeFileSync(join(legalOut, name), content);
|
|
125
48
|
}
|
|
@@ -208,12 +131,12 @@ function vitops(options = {}) {
|
|
|
208
131
|
};
|
|
209
132
|
if (req.method === "GET") try {
|
|
210
133
|
const current = readJson(input);
|
|
211
|
-
const at = designSystemPath(current, options.theme);
|
|
134
|
+
const at = designSystemPath$1(current, options.theme);
|
|
212
135
|
return json(200, {
|
|
213
136
|
writable: true,
|
|
214
137
|
path: input,
|
|
215
138
|
at,
|
|
216
|
-
config: getAt(current, at)
|
|
139
|
+
config: getAt$1(current, at)
|
|
217
140
|
});
|
|
218
141
|
} catch (err) {
|
|
219
142
|
return json(500, { error: err.message });
|
|
@@ -226,11 +149,11 @@ function vitops(options = {}) {
|
|
|
226
149
|
const { patch } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
227
150
|
if (patch == null || typeof patch !== "object") return json(400, { errors: ["body must be { patch: object }"] });
|
|
228
151
|
const current = readJson(input);
|
|
229
|
-
const at = designSystemPath(current, options.theme);
|
|
230
|
-
const merged =
|
|
152
|
+
const at = designSystemPath$1(current, options.theme);
|
|
153
|
+
const merged = mergePatch(getAt$1(current, at), patch);
|
|
231
154
|
const result = validate(merged);
|
|
232
155
|
if (!result.ok) return json(400, { errors: result.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`) });
|
|
233
|
-
writeFileSync(input, `${formatConfig(setAt(current, at, merged))}\n`);
|
|
156
|
+
writeFileSync(input, `${formatConfig(setAt$1(current, at, merged))}\n`);
|
|
234
157
|
return json(200, {
|
|
235
158
|
ok: true,
|
|
236
159
|
warnings: result.warnings
|
package/dist/index.mjs.map
CHANGED
|
@@ -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` (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 isConfig,\n resolveConfig,\n validate,\n type Format,\n type LegalOutput,\n} from '@getvitops/generator';\nimport { generateFavicons, writeFaviconManifest } from '@getvitops/utils';\n// Own subpath: it shells out to ffmpeg, and a project without a `media` option\n// should never load it.\nimport {\n MEDIA_MANIFEST_PATH,\n processMedia,\n type MediaConfig,\n type OutputKind,\n} from '@getvitops/utils/media';\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. `resolveConfig` 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 (!isConfig(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 VitopsMediaOptions {\n /** Directory of unprocessed video, walked recursively. Default: 'raw'. */\n raw?: string;\n /**\n * Where encoded outputs go. Default: `'src/assets/processed'`.\n *\n * Under `src/` so Vite content-hashes each file and it can be served immutable;\n * the raw sources stay outside anything Vite scans.\n */\n out?: string;\n /** Cap the output width, keeping aspect ratio. Default 1920; 0 disables. */\n maxWidth?: number;\n /** Quality on VP9's CRF scale (0–63, lower is better). Default 32. */\n crf?: number;\n /** Optional bitrate ceiling, e.g. `'2M'`. Absent means constant quality. */\n maxBitrate?: string;\n /** Keep the audio track. Default false. */\n audio?: boolean;\n /** Timestamp (seconds) the poster frame comes from. Default 0. */\n posterTime?: number;\n /** Which outputs to produce. Default: webm, mp4 and a poster. */\n outputs?: OutputKind[];\n /** Cache file. Default `.vitops/media-manifest.json`. */\n manifest?: string;\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 * Also encode raw video on build, into WebM + MP4 + a poster frame.\n *\n * Runs inside this pass rather than in the Astro integration's `config:setup`\n * hook, for the same reason the manifest and sprite copies do: it produces bytes,\n * so it belongs where it cannot race the first generation.\n *\n * Cached on source content and encode settings, so re-running costs a hash per\n * file. Needs `ffmpeg` on PATH and fails loudly without it — an unencoded hero\n * video is not a degraded build, it is a broken page.\n *\n * The raw directory is deliberately **not** watched: `watched()` matches config\n * files by exact path, and a multi-minute encode triggered mid-dev-server is a\n * poor trade for it. A video added during dev arrives on restart, or via\n * `vitops media`.\n */\n media?: VitopsMediaOptions;\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 = isConfig(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 = resolveConfig(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 if (options.media) {\n const media = options.media;\n const config: MediaConfig = {\n ...(media.maxWidth != null ? { maxWidth: media.maxWidth } : {}),\n ...(media.crf != null ? { crf: media.crf } : {}),\n ...(media.maxBitrate ? { maxBitrate: media.maxBitrate } : {}),\n ...(media.audio != null ? { audio: media.audio } : {}),\n ...(media.posterTime != null ? { posterTime: media.posterTime } : {}),\n ...(media.outputs ? { outputs: media.outputs } : {}),\n };\n const result = await processMedia({\n raw: resolve(root, media.raw ?? 'raw'),\n out: resolve(root, media.out ?? 'src/assets/processed'),\n config,\n // Resolved against the project root even when defaulted. Left to\n // `processMedia` it would resolve against cwd, and in a monorepo run from\n // the workspace root that puts the cache somewhere the next build won't\n // look — so every build re-encodes, silently.\n manifest: resolve(root, media.manifest ?? MEDIA_MANIFEST_PATH),\n // An encode is minutes of silence otherwise, which in a build reads as a hang.\n onProgress: (message) => console.log(`[vitops] media: ${message}`),\n });\n if (result.written.length > 0)\n console.log(\n `[vitops] media: ${result.written.length} written, ${result.skipped} unchanged`,\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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["designSystemPath","getAt","setAt"],"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 designSystemPath,\n formatConfig,\n generate,\n generateLegal,\n getAt,\n isConfig,\n mergePatch,\n resolveConfig,\n setAt,\n validate,\n type Format,\n type LegalOutput,\n} from '@getvitops/generator';\nimport { generateFavicons, writeFaviconManifest } from '@getvitops/utils';\n// Re-exported for existing consumers/tests importing these from this package\n// directly — the implementations now live in `@getvitops/generator` (shared\n// with the EmDash design-system-editor plugin route; see that package's\n// AGENTS.md section), this module just re-uses them.\nexport { designSystemPath, getAt, setAt } from '@getvitops/generator';\n// Own subpath: it shells out to ffmpeg, and a project without a `media` option\n// should never load it.\nimport {\n MEDIA_MANIFEST_PATH,\n processMedia,\n type MediaConfig,\n type OutputKind,\n} from '@getvitops/utils/media';\n\n/** Endpoint `<wc-theme-editor>` probes for, and POSTs its patch to. */\nconst EDITOR_ENDPOINT = '/__vitops/design-system';\n\nexport interface VitopsMediaOptions {\n /** Directory of unprocessed video, walked recursively. Default: 'raw'. */\n raw?: string;\n /**\n * Where encoded outputs go. Default: `'src/assets/processed'`.\n *\n * Under `src/` so Vite content-hashes each file and it can be served immutable;\n * the raw sources stay outside anything Vite scans.\n */\n out?: string;\n /** Cap the output width, keeping aspect ratio. Default 1920; 0 disables. */\n maxWidth?: number;\n /** Quality on VP9's CRF scale (0–63, lower is better). Default 32. */\n crf?: number;\n /** Optional bitrate ceiling, e.g. `'2M'`. Absent means constant quality. */\n maxBitrate?: string;\n /** Keep the audio track. Default false. */\n audio?: boolean;\n /** Timestamp (seconds) the poster frame comes from. Default 0. */\n posterTime?: number;\n /** Which outputs to produce. Default: webm, mp4 and a poster. */\n outputs?: OutputKind[];\n /** Cache file. Default `.vitops/media-manifest.json`. */\n manifest?: string;\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 * Also encode raw video on build, into WebM + MP4 + a poster frame.\n *\n * Runs inside this pass rather than in the Astro integration's `config:setup`\n * hook, for the same reason the manifest and sprite copies do: it produces bytes,\n * so it belongs where it cannot race the first generation.\n *\n * Cached on source content and encode settings, so re-running costs a hash per\n * file. Needs `ffmpeg` on PATH and fails loudly without it — an unencoded hero\n * video is not a degraded build, it is a broken page.\n *\n * The raw directory is deliberately **not** watched: `watched()` matches config\n * files by exact path, and a multi-minute encode triggered mid-dev-server is a\n * poor trade for it. A video added during dev arrives on restart, or via\n * `vitops media`.\n */\n media?: VitopsMediaOptions;\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 = isConfig(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 = resolveConfig(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 if (options.media) {\n const media = options.media;\n const config: MediaConfig = {\n ...(media.maxWidth != null ? { maxWidth: media.maxWidth } : {}),\n ...(media.crf != null ? { crf: media.crf } : {}),\n ...(media.maxBitrate ? { maxBitrate: media.maxBitrate } : {}),\n ...(media.audio != null ? { audio: media.audio } : {}),\n ...(media.posterTime != null ? { posterTime: media.posterTime } : {}),\n ...(media.outputs ? { outputs: media.outputs } : {}),\n };\n const result = await processMedia({\n raw: resolve(root, media.raw ?? 'raw'),\n out: resolve(root, media.out ?? 'src/assets/processed'),\n config,\n // Resolved against the project root even when defaulted. Left to\n // `processMedia` it would resolve against cwd, and in a monorepo run from\n // the workspace root that puts the cache somewhere the next build won't\n // look — so every build re-encodes, silently.\n manifest: resolve(root, media.manifest ?? MEDIA_MANIFEST_PATH),\n // An encode is minutes of silence otherwise, which in a build reads as a hang.\n onProgress: (message) => console.log(`[vitops] media: ${message}`),\n });\n if (result.written.length > 0)\n console.log(\n `[vitops] media: ${result.written.length} written, ${result.skipped} unchanged`,\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 = mergePatch(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": "8.0.
|
|
3
|
+
"version": "8.2.0-alpha.0",
|
|
4
4
|
"description": "Vite plugin that generates Vitops design-system outputs from a design-system.json during a Vite/Astro build.",
|
|
5
5
|
"homepage": "https://docs.vitops.ca",
|
|
6
6
|
"bugs": "https://github.com/getvitops/design-system/issues",
|
|
@@ -26,12 +26,11 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@getvitops/generator": "8.0.
|
|
30
|
-
"@getvitops/utils": "8.0.
|
|
29
|
+
"@getvitops/generator": "8.2.0-alpha.0",
|
|
30
|
+
"@getvitops/utils": "8.2.0-alpha.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"vite": "
|
|
34
|
-
"vitest": "4.1.9"
|
|
33
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.0.0"
|
|
35
34
|
},
|
|
36
35
|
"peerDependencies": {
|
|
37
36
|
"vite": ">=5"
|