@getvitops/vite 0.6.0 → 0.8.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 +120 -0
- package/README.md +6 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +92 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @getvitops/vite
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **New: `<wc-theme-editor>`, a live theme editor you can ship with your site.**
|
|
8
|
+
|
|
9
|
+
Tune the whole design system in the browser — palette, semantic roles, type roles, spacing, layout,
|
|
10
|
+
pattern geometry, radii, shadows — with no rebuild. Edits are layered as `:root` custom-property
|
|
11
|
+
overrides, persist to localStorage, and export as CSS or as a `design-system.json` patch. It follows
|
|
12
|
+
your site's colour scheme and drives the same `data-theme` + storage key as `<color-scheme-toggle>`,
|
|
13
|
+
so the two no longer fight.
|
|
14
|
+
|
|
15
|
+
It ships as a **separate, opt-in bundle** (`@getvitops/core/editor`, ~13 kB, no Lit) and is never
|
|
16
|
+
registered in `elements.js`: a page that doesn't ask for it pays nothing.
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// astro.config.mjs
|
|
20
|
+
getvitops({
|
|
21
|
+
css: { input: 'design-system.json', format: 'css', out: 'src/styles' },
|
|
22
|
+
editor: true,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<!-- anywhere in your layout -->
|
|
28
|
+
<wc-theme-editor></wc-theme-editor>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`editor: true` copies `editor.js` into `public/vitops/`, loads it from `<Head />`, and mirrors
|
|
32
|
+
`design-manifest.json` to `/vitops/design-manifest.json` (override with the `manifest` attribute).
|
|
33
|
+
Outside Astro, load the bundle yourself and point `manifest` at the file.
|
|
34
|
+
|
|
35
|
+
**Save straight back to your config, in dev.** `@getvitops/vite` now serves a dev-only
|
|
36
|
+
`/__vitops/design-system` endpoint: the editor POSTs its patch, the plugin deep-merges it into your
|
|
37
|
+
`design-system.json`, validates the _merged_ result, writes it back preserving the file's formatting,
|
|
38
|
+
then regenerates and reloads. The endpoint exists only under `vite dev` — on a static deploy the
|
|
39
|
+
editor detects its absence and hides the button, while everything else still works.
|
|
40
|
+
|
|
41
|
+
**Added:** `colors.roleTokens` in `design-manifest.json` — the functional token set (bg / border /
|
|
42
|
+
solid / on-solid / text / emphasis stops) precomputed per hue, in `default` and `surface` variants for
|
|
43
|
+
both appearances. Re-pointing a role at another hue needs these: `solid` is chosen by scanning the hue
|
|
44
|
+
and `on-solid` is a computed contrast value, so neither is derivable by a browser client.
|
|
45
|
+
|
|
46
|
+
**Removed:** the dead `interaction` block from `design-manifest.json` — a hardcoded
|
|
47
|
+
`{duration, easing}` literal with no schema key, no reverse-index entry, and no corresponding CSS
|
|
48
|
+
variable. Nothing could have consumed it meaningfully. The real animation knobs are
|
|
49
|
+
`--animation-duration-*` / `--custom-ease-*`.
|
|
50
|
+
|
|
51
|
+
**Fixed:** a `[popover]` reset in `popover.css` was stripping the background, padding and border from
|
|
52
|
+
any pattern used as a popover. It is imported after `drawer.css`, so at equal specificity it beat
|
|
53
|
+
`.drawer` — and `dialog` (0-0-1) too — leaving a `.drawer[popover]` transparent and unpadded despite
|
|
54
|
+
drawer.css documenting popover as supported. The reset now sits in `:where()` at zero specificity, so
|
|
55
|
+
it still overrides the UA stylesheet but any pattern class takes its surface back. Open/closed
|
|
56
|
+
behaviour is unchanged.
|
|
57
|
+
|
|
58
|
+
**Added:** `input[type="range"]` and `input[type="color"]` now have baseline styling in `forms.css`
|
|
59
|
+
(accent colour from `--ui-primary-solid`; the colour swatch picks up the control border and radius).
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- Updated dependencies
|
|
64
|
+
- @getvitops/generator@0.8.0
|
|
65
|
+
- @getvitops/utils@0.8.0
|
|
66
|
+
|
|
67
|
+
## 0.7.0
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- @getvitops/generator@0.7.0
|
|
72
|
+
- @getvitops/utils@0.7.0
|
|
73
|
+
|
|
74
|
+
## 0.6.0
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- Updated dependencies [2cc847d]
|
|
79
|
+
- @getvitops/generator@0.6.0
|
|
80
|
+
- @getvitops/utils@0.6.0
|
|
81
|
+
|
|
82
|
+
## 0.5.0
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies
|
|
87
|
+
- @getvitops/generator@0.5.0
|
|
88
|
+
- @getvitops/utils@0.5.0
|
|
89
|
+
|
|
90
|
+
## 0.4.0
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- Updated dependencies [d7e6491]
|
|
95
|
+
- @getvitops/utils@0.4.0
|
|
96
|
+
- @getvitops/generator@0.4.0
|
|
97
|
+
|
|
98
|
+
## 0.3.0
|
|
99
|
+
|
|
100
|
+
### Patch Changes
|
|
101
|
+
|
|
102
|
+
- Updated dependencies
|
|
103
|
+
- @getvitops/utils@0.3.0
|
|
104
|
+
- @getvitops/generator@0.3.0
|
|
105
|
+
|
|
106
|
+
## 0.2.1
|
|
107
|
+
|
|
108
|
+
### Patch Changes
|
|
109
|
+
|
|
110
|
+
- Updated dependencies [d28aae7]
|
|
111
|
+
- @getvitops/generator@0.2.1
|
|
112
|
+
- @getvitops/utils@0.2.1
|
|
113
|
+
|
|
114
|
+
## 0.2.0
|
|
115
|
+
|
|
116
|
+
### Patch Changes
|
|
117
|
+
|
|
118
|
+
- Updated dependencies
|
|
119
|
+
- @getvitops/generator@0.2.0
|
|
120
|
+
- @getvitops/utils@0.2.0
|
package/README.md
CHANGED
|
@@ -25,3 +25,9 @@ Then import the generated stylesheet (Tailwind v4):
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Powered by [`@getvitops/generator`](https://www.npmjs.com/package/@getvitops/generator).
|
|
28
|
+
|
|
29
|
+
## Changelog
|
|
30
|
+
|
|
31
|
+
This package's history: [`CHANGELOG.md`](./CHANGELOG.md) (shipped in the npm tarball, so it
|
|
32
|
+
also reads from `node_modules/@getvitops/vite/CHANGELOG.md`). `@getvitops/core`, `generator`,
|
|
33
|
+
`utils`, `cli`, `vite` and `astro` share one version and are released together.
|
package/dist/index.d.mts
CHANGED
|
@@ -25,6 +25,13 @@ interface VitopsPluginOptions {
|
|
|
25
25
|
out?: string;
|
|
26
26
|
/** Also generate a favicon set on build from this source image. */
|
|
27
27
|
favicon?: VitopsFaviconOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Directory to mirror `design-manifest.json` into as `vitops/design-manifest.json`
|
|
30
|
+
* — the URL `<wc-theme-editor>` fetches by default. Set by the Astro integration's
|
|
31
|
+
* `editor` option. Copied inside the generate pass rather than by the caller, so
|
|
32
|
+
* it can't race the first generation and can't go stale on regeneration.
|
|
33
|
+
*/
|
|
34
|
+
editorManifestDir?: string;
|
|
28
35
|
}
|
|
29
36
|
declare function vitops(options?: VitopsPluginOptions): Plugin;
|
|
30
37
|
//#endregion
|
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":";;;;UAqEiB,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;EAM8B;EAJ9B,MAAA,GAAS,MAAA;EAAT;EAEA,GAAA;EAAA;EAEA,OAAA,GAAU,oBAAoB;EAApB;;;AAOO;AAClB;;EADC,iBAAA;AAAA;AAAA,iBAGsB,MAAA,CAAO,OAAA,GAAS,mBAAA,GAA2B,MAAM"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { generate, validate } from "@getvitops/generator";
|
|
3
4
|
import { generateFavicons, writeFaviconManifest } from "@getvitops/utils";
|
|
4
5
|
|
|
5
6
|
//#region src/index.ts
|
|
@@ -13,6 +14,52 @@ import { generateFavicons, writeFaviconManifest } from "@getvitops/utils";
|
|
|
13
14
|
*
|
|
14
15
|
* Thin wrapper over @getvitops/generator — the same generator the CLI uses.
|
|
15
16
|
*/
|
|
17
|
+
/** Endpoint `<wc-theme-editor>` probes for, and POSTs its patch to. */
|
|
18
|
+
const EDITOR_ENDPOINT = "/__vitops/design-system";
|
|
19
|
+
/**
|
|
20
|
+
* Recursively merge a patch into a config. Plain objects merge key-wise;
|
|
21
|
+
* everything else (scalars, and arrays — a token list is replaced, not appended)
|
|
22
|
+
* is overwritten by the patch.
|
|
23
|
+
*/
|
|
24
|
+
function deepMerge(base, patch) {
|
|
25
|
+
const isPlain = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
26
|
+
if (!isPlain(base) || !isPlain(patch)) return patch;
|
|
27
|
+
const out = { ...base };
|
|
28
|
+
for (const [k, v] of Object.entries(patch)) out[k] = deepMerge(base[k], v);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/** Printed width the config is formatted to — matches the repo formatter's default. */
|
|
32
|
+
const PRINT_WIDTH = 100;
|
|
33
|
+
/**
|
|
34
|
+
* Serialize a config the way a formatter would: 2-space indent, but arrays of
|
|
35
|
+
* scalars kept on one line when they fit.
|
|
36
|
+
*
|
|
37
|
+
* `JSON.stringify(v, null, 2)` explodes every array onto its own lines, so writing
|
|
38
|
+
* back a two-key patch reflowed ~300 lines of `design-system.json` — a diff nobody
|
|
39
|
+
* can review, for a change of two values. Matching the prevailing style keeps the
|
|
40
|
+
* write surgical, which is the whole point of saving to source rather than copying
|
|
41
|
+
* a patch by hand.
|
|
42
|
+
*/
|
|
43
|
+
function formatConfig(value, col = 0, indent = 0) {
|
|
44
|
+
const pad = " ".repeat(indent);
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
if (!value.length) return "[]";
|
|
47
|
+
if (value.every((v) => v === null || typeof v !== "object")) {
|
|
48
|
+
const inline = `[${value.map((v) => JSON.stringify(v)).join(", ")}]`;
|
|
49
|
+
if (col + inline.length <= PRINT_WIDTH) return inline;
|
|
50
|
+
}
|
|
51
|
+
return `[\n${value.map((v) => `${pad} ${formatConfig(v, indent + 2, indent + 2)}`).join(",\n")}\n${pad}]`;
|
|
52
|
+
}
|
|
53
|
+
if (value !== null && typeof value === "object") {
|
|
54
|
+
const entries = Object.entries(value);
|
|
55
|
+
if (!entries.length) return "{}";
|
|
56
|
+
return `{\n${entries.map(([k, v]) => {
|
|
57
|
+
const head = `${pad} ${JSON.stringify(k)}: `;
|
|
58
|
+
return head + formatConfig(v, head.length, indent + 2);
|
|
59
|
+
}).join(",\n")}\n${pad}}`;
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify(value) ?? "null";
|
|
62
|
+
}
|
|
16
63
|
function vitops(options = {}) {
|
|
17
64
|
const format = options.format ?? "tailwind";
|
|
18
65
|
const outDir = options.out ?? "src/styles";
|
|
@@ -24,6 +71,14 @@ function vitops(options = {}) {
|
|
|
24
71
|
format,
|
|
25
72
|
outDir
|
|
26
73
|
});
|
|
74
|
+
if (options.editorManifestDir) {
|
|
75
|
+
const src = resolve(root, outDir, "design-manifest.json");
|
|
76
|
+
if (existsSync(src)) {
|
|
77
|
+
const dest = resolve(root, options.editorManifestDir, "vitops");
|
|
78
|
+
mkdirSync(dest, { recursive: true });
|
|
79
|
+
copyFileSync(src, join(dest, "design-manifest.json"));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
27
82
|
if (options.favicon) {
|
|
28
83
|
const faviconOut = resolve(root, options.favicon.out ?? "public");
|
|
29
84
|
await generateFavicons({
|
|
@@ -58,6 +113,41 @@ function vitops(options = {}) {
|
|
|
58
113
|
await run();
|
|
59
114
|
server.ws.send({ type: "full-reload" });
|
|
60
115
|
});
|
|
116
|
+
server.middlewares.use(EDITOR_ENDPOINT, (req, res, next) => {
|
|
117
|
+
const json = (status, body) => {
|
|
118
|
+
res.statusCode = status;
|
|
119
|
+
res.setHeader("content-type", "application/json");
|
|
120
|
+
res.end(JSON.stringify(body));
|
|
121
|
+
};
|
|
122
|
+
if (req.method === "GET") try {
|
|
123
|
+
return json(200, {
|
|
124
|
+
writable: true,
|
|
125
|
+
path: input,
|
|
126
|
+
config: JSON.parse(readFileSync(input, "utf8"))
|
|
127
|
+
});
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return json(500, { error: err.message });
|
|
130
|
+
}
|
|
131
|
+
if (req.method !== "POST") return next();
|
|
132
|
+
const chunks = [];
|
|
133
|
+
req.on("data", (c) => chunks.push(c));
|
|
134
|
+
req.on("end", () => {
|
|
135
|
+
try {
|
|
136
|
+
const { patch } = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
137
|
+
if (patch == null || typeof patch !== "object") return json(400, { errors: ["body must be { patch: object }"] });
|
|
138
|
+
const merged = deepMerge(JSON.parse(readFileSync(input, "utf8")), patch);
|
|
139
|
+
const result = validate(merged);
|
|
140
|
+
if (!result.ok) return json(400, { errors: result.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`) });
|
|
141
|
+
writeFileSync(input, `${formatConfig(merged)}\n`);
|
|
142
|
+
return json(200, {
|
|
143
|
+
ok: true,
|
|
144
|
+
warnings: result.warnings
|
|
145
|
+
});
|
|
146
|
+
} catch (err) {
|
|
147
|
+
return json(400, { errors: [err.message] });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
61
151
|
}
|
|
62
152
|
};
|
|
63
153
|
}
|
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` 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 { resolve } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { generate, type Format } from '@getvitops/generator';\nimport { generateFavicons, writeFaviconManifest } from '@getvitops/utils';\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\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 root = '';\n\n const run = async () => {\n await generate({ input, format, outDir });\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 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 },\n async buildStart() {\n this.addWatchFile(input);\n await run();\n },\n async watchChange(id) {\n if (resolve(id) === input) await run();\n },\n configureServer(server) {\n server.watcher.add(input);\n server.watcher.on('change', async (file) => {\n if (resolve(file) !== input) return;\n await run();\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyCA,SAAwB,OAAO,UAA+B,CAAC,GAAW;CACxE,MAAM,SAAiB,QAAQ,UAAU;CACzC,MAAM,SAAS,QAAQ,OAAO;CAC9B,IAAI,QAAQ,QAAQ,QAAQ,SAAS,oBAAoB;CACzD,IAAI,OAAO;CAEX,MAAM,MAAM,YAAY;EACtB,MAAM,SAAS;GAAE;GAAO;GAAQ;EAAO,CAAC;EACxC,IAAI,QAAQ,SAAS;GACnB,MAAM,aAAa,QAAQ,MAAM,QAAQ,QAAQ,OAAO,QAAQ;GAChE,MAAM,iBAAiB;IACrB,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,MAAM;IAC5C,WAAW;IACX,GAAI,QAAQ,QAAQ,eAChB,EAAE,cAAc,QAAQ,MAAM,QAAQ,QAAQ,YAAY,EAAE,IAC5D,CAAC;GACP,CAAC;GACD,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,YAC1C,MAAM,qBAAqB,YAAY;IACrC,MAAM,QAAQ,QAAQ;IACtB,YAAY,QAAQ,QAAQ;IAC5B,GAAI,QAAQ,QAAQ,kBAChB,EAAE,iBAAiB,QAAQ,QAAQ,gBAAgB,IACnD,CAAC;GACP,CAAC;EAEL;CACF;CAEA,OAAO;EACL,MAAM;EAEN,eAAe,QAAQ;GACrB,OAAO,OAAO;GACd,QAAQ,QAAQ,OAAO,MAAM,QAAQ,SAAS,oBAAoB;EACpE;EACA,MAAM,aAAa;GACjB,KAAK,aAAa,KAAK;GACvB,MAAM,IAAI;EACZ;EACA,MAAM,YAAY,IAAI;GACpB,IAAI,QAAQ,EAAE,MAAM,OAAO,MAAM,IAAI;EACvC;EACA,gBAAgB,QAAQ;GACtB,OAAO,QAAQ,IAAI,KAAK;GACxB,OAAO,QAAQ,GAAG,UAAU,OAAO,SAAS;IAC1C,IAAI,QAAQ,IAAI,MAAM,OAAO;IAC7B,MAAM,IAAI;IACV,OAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;GACxC,CAAC;EACH;CACF;AACF"}
|
|
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 { generate, validate, type Format } 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\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 root = '';\n\n const run = async () => {\n await generate({ input, format, outDir });\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 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 },\n async buildStart() {\n this.addWatchFile(input);\n await run();\n },\n async watchChange(id) {\n if (resolve(id) === input) await run();\n },\n configureServer(server) {\n server.watcher.add(input);\n server.watcher.on('change', async (file) => {\n if (resolve(file) !== input) 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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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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": [
|
|
7
|
-
"dist"
|
|
7
|
+
"dist",
|
|
8
|
+
"CHANGELOG.md"
|
|
8
9
|
],
|
|
9
10
|
"type": "module",
|
|
10
11
|
"exports": {
|
|
@@ -17,8 +18,8 @@
|
|
|
17
18
|
"access": "public"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@getvitops/
|
|
21
|
-
"@getvitops/
|
|
21
|
+
"@getvitops/utils": "0.8.0",
|
|
22
|
+
"@getvitops/generator": "0.8.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"vite": "^6.0.0"
|