@wippy-fe/vite-plugin 0.0.27 → 0.0.28

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.
@@ -0,0 +1,52 @@
1
+ import { Plugin } from 'vite';
2
+ export { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared';
3
+
4
+ /**
5
+ * Vite plugins shared across Wippy apps and web components.
6
+ *
7
+ * **Status: Draft 1 (preview).** API may change between minor releases.
8
+ */
9
+
10
+ interface WippyPackagePluginOptions {
11
+ /**
12
+ * Path to the project's `package.json`. Defaults to `package.json`
13
+ * relative to Vite's resolved root (which is the directory containing
14
+ * `vite.config.ts` for typical app layouts).
15
+ */
16
+ packageJsonPath?: string;
17
+ /**
18
+ * If `true`, fail the build when `package.json` cannot be read. If
19
+ * `false` (default), the plugin silently no-ops — useful for shared
20
+ * configs where some target apps don't need the injection.
21
+ */
22
+ required?: boolean;
23
+ }
24
+ /**
25
+ * Injects the consuming app's `package.json` content into `app.html` as
26
+ * `<script type="application/json" data-role="@wippy/package">`. The
27
+ * script is placed at the top of `<head>` so it lands in the DOM before
28
+ * `dev-proxy.js` runs in host-less mode.
29
+ *
30
+ * dev-proxy reads the JSON synchronously at boot to seed proxy injection
31
+ * defaults from `wippy.proxy.injections` and per-page theme defaults
32
+ * from `wippy.configOverrides.customization`. Under a real Wippy host
33
+ * the script tag is harmless metadata — the host reads `package.json`
34
+ * from the registry server-side and ignores the inline copy.
35
+ *
36
+ * Usage:
37
+ *
38
+ * ```ts
39
+ * // vite.config.ts
40
+ * import { defineConfig } from 'vite'
41
+ * import vue from '@vitejs/plugin-vue'
42
+ * import { wippyPackagePlugin } from '@wippy-fe/vite-plugin'
43
+ *
44
+ * export default defineConfig({
45
+ * plugins: [vue(), wippyPackagePlugin()],
46
+ * // …
47
+ * })
48
+ * ```
49
+ */
50
+ declare function wippyPackagePlugin(options?: WippyPackagePluginOptions): Plugin;
51
+
52
+ export { type WippyPackagePluginOptions, wippyPackagePlugin };
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ // src/index.ts
2
+ import { readFileSync } from "fs";
3
+ import { resolve } from "path";
4
+
5
+ // ../@wippy-fe--shared/src/index.ts
6
+ var WIPPY_PACKAGE_DATA_ROLE = "@wippy/package";
7
+
8
+ // src/index.ts
9
+ function wippyPackagePlugin(options = {}) {
10
+ let root;
11
+ return {
12
+ name: "@wippy-fe/vite-plugin:wippy-package",
13
+ enforce: "pre",
14
+ configResolved(config) {
15
+ root = config.root;
16
+ },
17
+ transformIndexHtml: {
18
+ order: "pre",
19
+ handler(html) {
20
+ const pkgPath = options.packageJsonPath ?? resolve(root, "package.json");
21
+ let raw;
22
+ try {
23
+ raw = readFileSync(pkgPath, "utf-8");
24
+ } catch (err) {
25
+ if (options.required)
26
+ throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${err.message}`);
27
+ return html;
28
+ }
29
+ const safeJson = raw.replace(/<\/script/gi, "<\\/script");
30
+ const tag = `<script type="application/json" data-role="${WIPPY_PACKAGE_DATA_ROLE}">${safeJson}</script>`;
31
+ if (html.includes("<head>"))
32
+ return html.replace("<head>", `<head>
33
+ ${tag}`);
34
+ return tag + html;
35
+ }
36
+ }
37
+ };
38
+ }
39
+ export {
40
+ WIPPY_PACKAGE_DATA_ROLE,
41
+ wippyPackagePlugin
42
+ };
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../../@wippy-fe--shared/src/index.ts"],"sourcesContent":["/**\n * Vite plugins shared across Wippy apps and web components.\n *\n * **Status: Draft 1 (preview).** API may change between minor releases.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\n\nexport { WIPPY_PACKAGE_DATA_ROLE }\n\nexport interface WippyPackagePluginOptions {\n /**\n * Path to the project's `package.json`. Defaults to `package.json`\n * relative to Vite's resolved root (which is the directory containing\n * `vite.config.ts` for typical app layouts).\n */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` cannot be read. If\n * `false` (default), the plugin silently no-ops — useful for shared\n * configs where some target apps don't need the injection.\n */\n required?: boolean\n}\n\n/**\n * Injects the consuming app's `package.json` content into `app.html` as\n * `<script type=\"application/json\" data-role=\"@wippy/package\">`. The\n * script is placed at the top of `<head>` so it lands in the DOM before\n * `dev-proxy.js` runs in host-less mode.\n *\n * dev-proxy reads the JSON synchronously at boot to seed proxy injection\n * defaults from `wippy.proxy.injections` and per-page theme defaults\n * from `wippy.configOverrides.customization`. Under a real Wippy host\n * the script tag is harmless metadata — the host reads `package.json`\n * from the registry server-side and ignores the inline copy.\n *\n * Usage:\n *\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite'\n * import vue from '@vitejs/plugin-vue'\n * import { wippyPackagePlugin } from '@wippy-fe/vite-plugin'\n *\n * export default defineConfig({\n * plugins: [vue(), wippyPackagePlugin()],\n * // …\n * })\n * ```\n */\nexport function wippyPackagePlugin(options: WippyPackagePluginOptions = {}): Plugin {\n let root: string\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-package',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n },\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let raw: string\n try {\n raw = readFileSync(pkgPath, 'utf-8')\n }\n catch (err) {\n if (options.required)\n throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${(err as Error).message}`)\n return html\n }\n // Defang `</script>` sequences inside the JSON so the script tag\n // can't be terminated early. JSON.stringify(JSON.parse(raw))\n // would also collapse whitespace; we keep formatting for\n // readability and just escape the close tag.\n const safeJson = raw.replace(/<\\/script/gi, '<\\\\/script')\n const tag = `<script type=\"application/json\" data-role=\"${WIPPY_PACKAGE_DATA_ROLE}\">${safeJson}</script>`\n if (html.includes('<head>'))\n return html.replace('<head>', `<head>\\n ${tag}`)\n // No <head> tag — best effort prepend.\n return tag + html\n },\n },\n }\n}\n","/**\n * Cross-boundary contracts shared between the gen-2-chat host and the\n * `@wippy-fe/*` consumer packages.\n *\n * **Pure types and string-name constants.** No runtime code, no\n * `@wippy-fe/proxy` dependency. Importing from this module never loads\n * the proxy globals checker — it is safe to import from anywhere in the\n * host source tree without tripping the `host-import-guard` Vite plugin\n * (this package is on the allowlist).\n *\n * **Status: Draft 1 (preview).** Field names and shapes may change\n * between minor releases until a production consumer drives the corner\n * cases out.\n */\n\n// ─── Layout bus envelopes ────────────────────────────────────────────\n\n/**\n * In-tab broadcast envelope. Same shape on both sides of the bus —\n * iframe panels, host-mounted web components, and headless services\n * all see this object.\n */\nexport interface BroadcastEnvelope {\n channel: string\n payload: unknown\n sourcePanelId: string | null\n targetPanelId: string | null\n}\n\n/**\n * Panel-bound layout bus exposed on `el.__wippyHostBus` for web\n * components mounted as `kind: 'component'` panels. Source / target\n * attribution is set by the host — publishers cannot spoof it.\n */\nexport interface LayoutBusBound {\n /** 1:N broadcast — every other panel receives, sender excluded. */\n broadcast: (channel: string, payload: unknown) => void\n /** 1:1 send — only `target` receives. Unknown targets are no-ops. */\n send: (target: string, channel: string, payload: unknown) => void\n /** Subscribe; returns an `off()` for cleanup. */\n subscribe: (handler: (env: BroadcastEnvelope) => void) => () => void\n}\n\n// ─── Layout mutation targets ─────────────────────────────────────────\n\n/**\n * Drop target positions (relative to a slot rect). Reserved for the\n * future panelDrag capability; declared in v1 for forward compat.\n */\nexport type DropPosition = 'left' | 'right' | 'top' | 'bottom' | 'center' | 'none'\n\n/**\n * Slot size grammar (mirrors `@wippy-fe/layout` `SizeValue`). Tight\n * whitelist — `clamp()`, `minmax()`, `min-content`, `calc()`, em/rem/\n * vh/vw are rejected at validateLayout time. See `@wippy-fe/layout`\n * `helpers.ts` \"Size grammar\" for rationale.\n */\nexport type SizeValue\n = | 'auto'\n | `${number}fr`\n | `${number}px`\n | `${number}%`\n\n/** Pixel-only size, used for min/max and drawer dimensions. */\nexport type PixelSize = `${number}px`\n\n/**\n * Target for addPanel / movePanel. `relativeTo` names a panel already in\n * the tree; `position` picks the drop relative to that panel's rect.\n * Omit `relativeTo` to attach to the root tree (position becomes\n * direction-aware).\n */\nexport interface PanelTarget {\n relativeTo?: string\n position: DropPosition\n size?: SizeValue\n}\n\n// ─── Global-name constants ───────────────────────────────────────────\n//\n// These literal strings name the four `window.*` globals that the host\n// writes and consumer code reads. Both sides MUST agree on the spelling\n// — moving a typo here would silently break communication across the\n// host/proxy boundary. No `Window` augmentation is declared in this\n// package because the augmentation references host-side types\n// (`AppConfig`, `ProxyApiInstance`); host-side `src/shared/constants/global.ts`\n// keeps the `declare global` block.\n\nexport const GLOBAL_CONFIG_VAR = '__WIPPY_APP_CONFIG__' as const\nexport const GLOBAL_PROXY_CONFIG_VAR = '__WIPPY_PROXY_CONFIG__' as const\nexport const GLOBAL_API_PROVIDER = '__WIPPY_APP_API__' as const\nexport const GLOBAL_WEB_COMPONENT_CACHE = '__WIPPY_WEB_COMPONENT_CACHE__' as const\n\n// ─── HTML data-role markers ─────────────────────────────────────────\n//\n// `data-role` attributes that the host and dev-proxy use to find anchor\n// elements in the consuming app's `app.html`. The host removes / inserts\n// scripts at the `@wippy/scripts` element; dev-proxy reads the consuming\n// app's package.json from the `@wippy/package` JSON script (injected at\n// build time by `@wippy-fe/vite-plugin`'s `wippyPackagePlugin()`).\n\nexport const WIPPY_SCRIPTS_DATA_ROLE = '@wippy/scripts' as const\nexport const WIPPY_PACKAGE_DATA_ROLE = '@wippy/package' as const\n"],"mappings":";AAOA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;;;AC8FjB,IAAM,0BAA0B;;;ADhDhC,SAAS,mBAAmB,UAAqC,CAAC,GAAW;AAClF,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAwB;AACrC,aAAO,OAAO;AAAA,IAChB;AAAA,IACA,oBAAoB;AAAA,MAClB,OAAO;AAAA,MACP,QAAQ,MAAM;AACZ,cAAM,UAAU,QAAQ,mBAAmB,QAAQ,MAAM,cAAc;AACvE,YAAI;AACJ,YAAI;AACF,gBAAM,aAAa,SAAS,OAAO;AAAA,QACrC,SACO,KAAK;AACV,cAAI,QAAQ;AACV,kBAAM,IAAI,MAAM,0CAA0C,OAAO,KAAM,IAAc,OAAO,EAAE;AAChG,iBAAO;AAAA,QACT;AAKA,cAAM,WAAW,IAAI,QAAQ,eAAe,YAAY;AACxD,cAAM,MAAM,8CAA8C,uBAAuB,KAAK,QAAQ;AAC9F,YAAI,KAAK,SAAS,QAAQ;AACxB,iBAAO,KAAK,QAAQ,UAAU;AAAA,MAAe,GAAG,EAAE;AAEpD,eAAO,MAAM;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,20 +1,24 @@
1
1
  {
2
2
  "name": "@wippy-fe/vite-plugin",
3
3
  "type": "module",
4
- "version": "0.0.27",
4
+ "version": "0.0.28",
5
5
  "description": "Vite plugins for Wippy apps and web components — package metadata injection, future build-time helpers.",
6
6
  "license": "UNLICENSED",
7
7
  "sideEffects": false,
8
- "main": "src/index.ts",
9
- "types": "src/index.ts",
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.js",
10
+ "types": "dist/index.d.ts",
10
11
  "files": [
11
12
  "package.json",
12
- "src/"
13
+ "dist/"
13
14
  ],
14
15
  "peerDependencies": {
15
16
  "vite": "^5 || ^6 || ^7"
16
17
  },
17
- "dependencies": {
18
- "@wippy-fe/shared": "0.0.27"
18
+ "devDependencies": {
19
+ "@wippy-fe/shared": "0.0.28"
20
+ },
21
+ "scripts": {
22
+ "build": "tsup"
19
23
  }
20
24
  }
package/src/index.test.ts DELETED
@@ -1,84 +0,0 @@
1
- /**
2
- * @vitest-environment node
3
- *
4
- * Vite plugin runs at build time and reads the file system — node env,
5
- * not jsdom. We mock `node:fs.readFileSync` so the test doesn't touch
6
- * the actual repo's package.json.
7
- */
8
- import type { IndexHtmlTransformContext } from 'vite'
9
- import { describe, expect, it, vi } from 'vitest'
10
- import { wippyPackagePlugin } from './index'
11
-
12
- vi.mock('node:fs', () => ({
13
- readFileSync: vi.fn(),
14
- }))
15
-
16
- const { readFileSync } = await import('node:fs')
17
-
18
- interface InvokablePlugin {
19
- configResolved?: (config: unknown) => void
20
- transformIndexHtml: {
21
- handler: (html: string, ctx?: IndexHtmlTransformContext) => string
22
- }
23
- }
24
-
25
- function invokePlugin(html: string, packageJson: string | Error): string {
26
- if (packageJson instanceof Error) {
27
- vi.mocked(readFileSync).mockImplementationOnce(() => {
28
- throw packageJson
29
- })
30
- }
31
- else {
32
- vi.mocked(readFileSync).mockReturnValueOnce(packageJson)
33
- }
34
-
35
- const plugin = wippyPackagePlugin() as unknown as InvokablePlugin
36
- plugin.configResolved?.({ root: '/fake' })
37
- return plugin.transformIndexHtml.handler(html)
38
- }
39
-
40
- describe('wippyPackagePlugin', () => {
41
- it('injects the package JSON as a data-role script at the top of <head>', () => {
42
- const out = invokePlugin('<html><head><meta charset="utf-8"></head></html>', '{"name":"demo"}')
43
- expect(out).toContain('<script type="application/json" data-role="@wippy/package">{"name":"demo"}</script>')
44
- expect(out.indexOf('data-role="@wippy/package"')).toBeLessThan(out.indexOf('charset'))
45
- })
46
-
47
- it('defangs `</script>` sequences inside the JSON so the tag cannot be terminated early', () => {
48
- const malicious = '{"trick": "</script><img src=x onerror=alert(1)>"}'
49
- const out = invokePlugin('<html><head></head></html>', malicious)
50
- // The literal `</script` must not survive intact between the script
51
- // tag we opened and the matching close — otherwise the browser
52
- // would parse the rest of the JSON as HTML.
53
- const inside = out.split('data-role="@wippy/package">')[1].split('</script>')[0]
54
- expect(inside).not.toContain('</script')
55
- expect(inside).toContain('<\\/script')
56
- })
57
-
58
- it('defangs case-insensitively (`</SCRIPT`, `</Script`)', () => {
59
- const out = invokePlugin('<html><head></head></html>', '{"a":"</SCRIPT</Script"}')
60
- const inside = out.split('data-role="@wippy/package">')[1].split('</script>')[0]
61
- expect(inside).not.toMatch(/<\/script/i)
62
- })
63
-
64
- it('silently no-ops when package.json is missing and required is false (default)', () => {
65
- const html = '<html><head></head></html>'
66
- const out = invokePlugin(html, new Error('ENOENT'))
67
- expect(out).toBe(html)
68
- expect(out).not.toContain('@wippy/package')
69
- })
70
-
71
- it('throws when package.json is missing and required: true', () => {
72
- vi.mocked(readFileSync).mockImplementationOnce(() => {
73
- throw new Error('ENOENT: missing')
74
- })
75
- const plugin = wippyPackagePlugin({ required: true }) as unknown as InvokablePlugin
76
- plugin.configResolved?.({ root: '/fake' })
77
- expect(() => plugin.transformIndexHtml.handler('<html><head></head></html>')).toThrow(/ENOENT/)
78
- })
79
-
80
- it('best-effort prepends the script tag when <head> is missing', () => {
81
- const out = invokePlugin('<html><body>no head</body></html>', '{"name":"x"}')
82
- expect(out.startsWith('<script type="application/json" data-role="@wippy/package">')).toBe(true)
83
- })
84
- })
package/src/index.ts DELETED
@@ -1,90 +0,0 @@
1
- /**
2
- * Vite plugins shared across Wippy apps and web components.
3
- *
4
- * **Status: Draft 1 (preview).** API may change between minor releases.
5
- */
6
-
7
- import type { Plugin, ResolvedConfig } from 'vite'
8
- import { readFileSync } from 'node:fs'
9
- import { resolve } from 'node:path'
10
- import { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'
11
-
12
- export { WIPPY_PACKAGE_DATA_ROLE }
13
-
14
- export interface WippyPackagePluginOptions {
15
- /**
16
- * Path to the project's `package.json`. Defaults to `package.json`
17
- * relative to Vite's resolved root (which is the directory containing
18
- * `vite.config.ts` for typical app layouts).
19
- */
20
- packageJsonPath?: string
21
- /**
22
- * If `true`, fail the build when `package.json` cannot be read. If
23
- * `false` (default), the plugin silently no-ops — useful for shared
24
- * configs where some target apps don't need the injection.
25
- */
26
- required?: boolean
27
- }
28
-
29
- /**
30
- * Injects the consuming app's `package.json` content into `app.html` as
31
- * `<script type="application/json" data-role="@wippy/package">`. The
32
- * script is placed at the top of `<head>` so it lands in the DOM before
33
- * `dev-proxy.js` runs in host-less mode.
34
- *
35
- * dev-proxy reads the JSON synchronously at boot to seed proxy injection
36
- * defaults from `wippy.proxy.injections` and per-page theme defaults
37
- * from `wippy.configOverrides.customization`. Under a real Wippy host
38
- * the script tag is harmless metadata — the host reads `package.json`
39
- * from the registry server-side and ignores the inline copy.
40
- *
41
- * Usage:
42
- *
43
- * ```ts
44
- * // vite.config.ts
45
- * import { defineConfig } from 'vite'
46
- * import vue from '@vitejs/plugin-vue'
47
- * import { wippyPackagePlugin } from '@wippy-fe/vite-plugin'
48
- *
49
- * export default defineConfig({
50
- * plugins: [vue(), wippyPackagePlugin()],
51
- * // …
52
- * })
53
- * ```
54
- */
55
- export function wippyPackagePlugin(options: WippyPackagePluginOptions = {}): Plugin {
56
- let root: string
57
-
58
- return {
59
- name: '@wippy-fe/vite-plugin:wippy-package',
60
- enforce: 'pre',
61
- configResolved(config: ResolvedConfig) {
62
- root = config.root
63
- },
64
- transformIndexHtml: {
65
- order: 'pre',
66
- handler(html) {
67
- const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')
68
- let raw: string
69
- try {
70
- raw = readFileSync(pkgPath, 'utf-8')
71
- }
72
- catch (err) {
73
- if (options.required)
74
- throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${(err as Error).message}`)
75
- return html
76
- }
77
- // Defang `</script>` sequences inside the JSON so the script tag
78
- // can't be terminated early. JSON.stringify(JSON.parse(raw))
79
- // would also collapse whitespace; we keep formatting for
80
- // readability and just escape the close tag.
81
- const safeJson = raw.replace(/<\/script/gi, '<\\/script')
82
- const tag = `<script type="application/json" data-role="${WIPPY_PACKAGE_DATA_ROLE}">${safeJson}</script>`
83
- if (html.includes('<head>'))
84
- return html.replace('<head>', `<head>\n ${tag}`)
85
- // No <head> tag — best effort prepend.
86
- return tag + html
87
- },
88
- },
89
- }
90
- }