@wippy-fe/vite-plugin 0.0.31 → 0.0.34

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/dist/index.d.ts CHANGED
@@ -2,51 +2,130 @@ import { Plugin } from 'vite';
2
2
  export { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared';
3
3
 
4
4
  /**
5
- * Vite plugins shared across Wippy apps and web components.
5
+ * `wippyPagePlugin` — for `view.page` packages (those with `wippy.type === 'page'`).
6
6
  *
7
- * **Status: Draft 1 (preview).** API may change between minor releases.
7
+ * Two outputs from a single resolved JSON source:
8
+ * 1. An inline `<script type="application/json" data-role="@wippy/package">`
9
+ * injected at the top of `app.html`'s `<head>`. Read at boot by
10
+ * host-less dev-proxy.
11
+ * 2. `wippy-meta.json` emitted into Vite's `outDir`. Read at request time
12
+ * by `wippy/views` in wippy-hosted mode.
13
+ *
14
+ * Both contain the consumer's `package.json` with every `file://<relative>`
15
+ * reference replaced by its file contents. Single source = identical render
16
+ * in either mode.
8
17
  */
9
18
 
10
- interface WippyPackagePluginOptions {
19
+ interface WippyPagePluginOptions {
20
+ /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */
21
+ packageJsonPath?: string;
11
22
  /**
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).
23
+ * If `true`, fail the build when `package.json` is missing. If `false`
24
+ * (default), the plugin silently no-ops — useful for shared configs
25
+ * where some target apps don't need the injection.
15
26
  */
27
+ required?: boolean;
28
+ }
29
+ declare function wippyPagePlugin(options?: WippyPagePluginOptions): Plugin;
30
+
31
+ /**
32
+ * `wippyComponentPlugin` — for `view.component` packages (those with
33
+ * `wippy.type === 'component'`).
34
+ *
35
+ * Components ship a JS module, not an HTML entry — there's nothing to inject
36
+ * an inline `<script>` into. The plugin's only job is to emit `wippy-meta.json`
37
+ * into Vite's `outDir` for `wippy/views` to read at request time.
38
+ *
39
+ * The `wippy-meta.json` file is the MANDATORY contract between components and
40
+ * `wippy/views`. See `web_components.spec.md` § "Package metadata: source of
41
+ * truth". Without it, `views` falls back to its legacy YAML-synthesis path,
42
+ * which is deprecated and exists only for migration.
43
+ */
44
+
45
+ interface WippyComponentPluginOptions {
46
+ /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */
16
47
  packageJsonPath?: string;
17
48
  /**
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.
49
+ * If `true`, fail the build when `package.json` is missing. If `false`
50
+ * (default), the plugin silently no-ops.
21
51
  */
22
52
  required?: boolean;
23
53
  }
54
+ declare function wippyComponentPlugin(options?: WippyComponentPluginOptions): Plugin;
55
+
56
+ /**
57
+ * Validate a consumer's `package.json` against the `wippy-component-1.0`
58
+ * spec FOR A PAGE PACKAGE. Throws with a clear, actionable message on any
59
+ * violation. Called by `wippyPagePlugin` at build time so misconfigurations
60
+ * fail the build instead of silently shipping broken metadata.
61
+ *
62
+ * We throw rather than warn because nobody reads build warnings — they
63
+ * scroll past in CI output and get filtered out of IDE diagnostics. A
64
+ * field-shape mistake that warns today is a real bug tomorrow.
65
+ *
66
+ * Rules enforced (must all hold for a page):
67
+ * - Top-level `name` and `version` must be strings.
68
+ * - `wippy` must be a plain object.
69
+ * - `wippy.type`, when set, MUST equal `'page'` (not `'component'`,
70
+ * `'widget'`, or anything else).
71
+ * - `wippy.path` is REQUIRED for pages (HTML entry path).
72
+ * - `wippy.tagName` is FORBIDDEN for pages (custom-element-tag is a
73
+ * component-only concept).
74
+ * - `specification`, when set, MUST equal `'wippy-component-1.0'`.
75
+ */
76
+ declare function validatePagePackage(pkg: Record<string, unknown>, packageJsonPath: string): void;
77
+ /**
78
+ * Validate a consumer's `package.json` for a component/widget package using
79
+ * `wippyComponentPlugin`. Throws on any violation. Same rationale as
80
+ * `validatePagePackage` — fail loud at build time.
81
+ *
82
+ * Rules enforced (must all hold for a component):
83
+ * - Top-level `name` and `version` must be strings.
84
+ * - `wippy` must be a plain object.
85
+ * - `wippy.type`, when set, MUST be one of `'component'` or `'widget'` (the
86
+ * spec uses both interchangeably for ESM-shipped custom elements).
87
+ * - `wippy.tagName` is REQUIRED for components — without it the host has
88
+ * no element name to register against.
89
+ * - `wippy.path` is FORBIDDEN for components (HTML-entry is a page-only
90
+ * concept; components use top-level `browser` for the ESM entry).
91
+ * - `specification`, when set, MUST equal `'wippy-component-1.0'`.
92
+ * - `browser` SHOULD be set (the ESM entry path); only warn-by-throw if
93
+ * absent because the plugin still emits useful meta without it.
94
+ */
95
+ declare function validateComponentPackage(pkg: Record<string, unknown>, packageJsonPath: string): void;
96
+
97
+ /**
98
+ * Vite plugins for Wippy view.page and view.component packages.
99
+ *
100
+ * Both plugins emit `dist/wippy-meta.json` — the consumer's `package.json`
101
+ * with every `file://<relative-path>` reference replaced by the referenced
102
+ * file's UTF-8 contents. `wippy-meta.json` is the MANDATORY contract between
103
+ * consumers and `wippy/views` ≥ 1.0.31. See `web_components.spec.md` §
104
+ * "Package metadata: source of truth" for the full spec.
105
+ *
106
+ * - `wippyPagePlugin()` — for `view.page` packages. Emits `wippy-meta.json`
107
+ * AND injects an inline `<script data-role="@wippy/package">` into the
108
+ * built `app.html` (for host-less mode).
109
+ * - `wippyComponentPlugin()` — for `view.component` packages. Emits
110
+ * `wippy-meta.json` only; components have no HTML entry.
111
+ *
112
+ * Convention: bulky string fields (`customCSS`, `description`, etc.) should
113
+ * be moved into separate files referenced via `file://` and named
114
+ * `<field-name-kebab>.do-not-link.<ext>` — basename matches the kebab-case
115
+ * wippy block field name (e.g. `customCSS` → `custom-css.do-not-link.css`,
116
+ * `description` → `description.do-not-link.md`). The `.do-not-link.` infix
117
+ * is what stops AI agents from `<link>`-ing the file directly into app.html.
118
+ * The proxy injects the content at runtime from the resolved package metadata.
119
+ *
120
+ * **Status: Draft 1 (preview).** API may change between minor releases.
121
+ */
122
+
24
123
  /**
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
- * ```
124
+ * @deprecated Use `wippyPagePlugin()` instead. The new name disambiguates
125
+ * the page-side plugin from `wippyComponentPlugin()` introduced alongside it.
126
+ * This alias preserves backwards compatibility for existing `vite.config.ts`
127
+ * files and will be removed in a future major.
49
128
  */
50
- declare function wippyPackagePlugin(options?: WippyPackagePluginOptions): Plugin;
129
+ declare function wippyPackagePlugin(...args: Parameters<typeof wippyPagePlugin>): Plugin;
51
130
 
52
- export { type WippyPackagePluginOptions, wippyPackagePlugin };
131
+ export { type WippyComponentPluginOptions, type WippyPagePluginOptions as WippyPackagePluginOptions, type WippyPagePluginOptions, validateComponentPackage, validatePagePackage, wippyComponentPlugin, wippyPackagePlugin, wippyPagePlugin };
package/dist/index.js CHANGED
@@ -1,43 +1,217 @@
1
- // src/index.ts
2
- import { readFileSync } from "fs";
3
- import { resolve } from "path";
4
-
5
1
  // ../@wippy-fe--shared/src/index.ts
6
2
  var WIPPY_PACKAGE_DATA_ROLE = "@wippy/package";
7
3
 
8
- // src/index.ts
9
- function wippyPackagePlugin(options = {}) {
4
+ // src/page-plugin.ts
5
+ import { resolve as resolve2 } from "path";
6
+
7
+ // src/shared.ts
8
+ import { readFileSync } from "fs";
9
+ import { basename, dirname, resolve } from "path";
10
+ function loadPackage(packageJsonPath) {
11
+ const raw = readFileSync(packageJsonPath, "utf-8");
12
+ const pkg = JSON.parse(raw);
13
+ return { pkg, baseDir: dirname(packageJsonPath) };
14
+ }
15
+ function kebabize(camel) {
16
+ return camel.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
17
+ }
18
+ function expectedDoNotLinkBasename(fieldName, actualBasename) {
19
+ if (!fieldName)
20
+ return null;
21
+ const dot = actualBasename.lastIndexOf(".");
22
+ if (dot <= 0)
23
+ return `${kebabize(fieldName)}.do-not-link.<ext>`;
24
+ const ext = actualBasename.slice(dot + 1);
25
+ const expected = `${kebabize(fieldName)}.do-not-link.${ext}`;
26
+ if (actualBasename === expected)
27
+ return null;
28
+ return expected;
29
+ }
30
+ function resolveFileRefs(node, baseDir, onWarn, fieldName = null) {
31
+ if (typeof node === "string" && node.startsWith("file://")) {
32
+ const rel = node.slice("file://".length);
33
+ const abs = resolve(baseDir, rel);
34
+ if (onWarn) {
35
+ const expected = expectedDoNotLinkBasename(fieldName, basename(rel));
36
+ if (expected) {
37
+ onWarn(`[@wippy-fe/vite-plugin] file:// basename "${basename(rel)}" for field "${fieldName}" does not match the *.do-not-link.<ext> convention. Expected: "${expected}". See web_components.spec.md \xA7 "Package metadata: source of truth".`);
38
+ }
39
+ }
40
+ return readFileSync(abs, "utf-8");
41
+ }
42
+ if (Array.isArray(node))
43
+ return node.map((v) => resolveFileRefs(v, baseDir, onWarn, fieldName));
44
+ if (node && typeof node === "object") {
45
+ return Object.fromEntries(
46
+ Object.entries(node).map(([k, v]) => [k, resolveFileRefs(v, baseDir, onWarn, k)])
47
+ );
48
+ }
49
+ return node;
50
+ }
51
+ function defangScriptClose(json) {
52
+ return json.replace(/<\/script/gi, "<\\/script");
53
+ }
54
+ function validatePagePackage(pkg, packageJsonPath) {
55
+ const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`;
56
+ if (typeof pkg.name !== "string" || pkg.name.length === 0)
57
+ throw new Error(`${prefix} \`name\` is required and must be a non-empty string`);
58
+ if (typeof pkg.version !== "string" || pkg.version.length === 0)
59
+ throw new Error(`${prefix} \`version\` is required and must be a non-empty string`);
60
+ if (pkg.specification !== void 0 && pkg.specification !== "wippy-component-1.0")
61
+ throw new Error(`${prefix} \`specification\` must be "wippy-component-1.0" (got ${JSON.stringify(pkg.specification)})`);
62
+ const wippy = pkg.wippy;
63
+ if (wippy === void 0 || wippy === null)
64
+ throw new Error(`${prefix} top-level \`wippy\` block is required for a page package using wippyPagePlugin()`);
65
+ if (typeof wippy !== "object" || Array.isArray(wippy))
66
+ throw new Error(`${prefix} \`wippy\` must be a JSON object (got ${Array.isArray(wippy) ? "array" : typeof wippy})`);
67
+ const w = wippy;
68
+ if (w.type !== void 0 && w.type !== "page")
69
+ throw new Error(`${prefix} \`wippy.type\` is "${String(w.type)}" but wippyPagePlugin() was used. Either set \`wippy.type: "page"\` or switch to wippyComponentPlugin() for components/widgets.`);
70
+ if (w.path === void 0)
71
+ throw new Error(`${prefix} \`wippy.path\` is required for a page package (the relative path to the HTML entry, e.g. "dist/app.html")`);
72
+ if (typeof w.path !== "string" || w.path.length === 0)
73
+ throw new Error(`${prefix} \`wippy.path\` must be a non-empty string (got ${JSON.stringify(w.path)})`);
74
+ if (w.tagName !== void 0)
75
+ throw new Error(`${prefix} \`wippy.tagName\` is set on a page package but the field is component-only. Either remove it or switch to wippyComponentPlugin().`);
76
+ }
77
+ function validateComponentPackage(pkg, packageJsonPath) {
78
+ const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`;
79
+ if (typeof pkg.name !== "string" || pkg.name.length === 0)
80
+ throw new Error(`${prefix} \`name\` is required and must be a non-empty string`);
81
+ if (typeof pkg.version !== "string" || pkg.version.length === 0)
82
+ throw new Error(`${prefix} \`version\` is required and must be a non-empty string`);
83
+ if (pkg.specification !== void 0 && pkg.specification !== "wippy-component-1.0")
84
+ throw new Error(`${prefix} \`specification\` must be "wippy-component-1.0" (got ${JSON.stringify(pkg.specification)})`);
85
+ const wippy = pkg.wippy;
86
+ if (wippy === void 0 || wippy === null)
87
+ throw new Error(`${prefix} top-level \`wippy\` block is required for a component package using wippyComponentPlugin()`);
88
+ if (typeof wippy !== "object" || Array.isArray(wippy))
89
+ throw new Error(`${prefix} \`wippy\` must be a JSON object (got ${Array.isArray(wippy) ? "array" : typeof wippy})`);
90
+ const w = wippy;
91
+ if (w.type !== void 0 && w.type !== "component" && w.type !== "widget")
92
+ throw new Error(`${prefix} \`wippy.type\` is "${String(w.type)}" but wippyComponentPlugin() was used. Either set \`wippy.type: "component"\` (or "widget") or switch to wippyPagePlugin() for pages.`);
93
+ if (w.tagName === void 0)
94
+ throw new Error(`${prefix} \`wippy.tagName\` is required for a component package (the custom-element tag name, e.g. "example-mermaid"). Without it the host cannot register the element.`);
95
+ if (typeof w.tagName !== "string" || w.tagName.length === 0)
96
+ throw new Error(`${prefix} \`wippy.tagName\` must be a non-empty string (got ${JSON.stringify(w.tagName)})`);
97
+ if (!/^[a-z][\da-z]*-[-\da-z]*$/.test(w.tagName))
98
+ throw new Error(`${prefix} \`wippy.tagName\` "${w.tagName}" is not a valid Custom Elements name. Must start with an ASCII lowercase letter, contain a hyphen, and use only lowercase letters, digits, and hyphens. Examples: "example-mermaid", "my-widget-v2".`);
99
+ const reserved = /* @__PURE__ */ new Set([
100
+ "annotation-xml",
101
+ "color-profile",
102
+ "font-face",
103
+ "font-face-src",
104
+ "font-face-uri",
105
+ "font-face-format",
106
+ "font-face-name",
107
+ "missing-glyph"
108
+ ]);
109
+ if (reserved.has(w.tagName))
110
+ throw new Error(`${prefix} \`wippy.tagName\` "${w.tagName}" is a reserved HTML name; browsers will refuse to register it.`);
111
+ if (w.path !== void 0)
112
+ throw new Error(`${prefix} \`wippy.path\` is set on a component package but the field is page-only. Components use top-level \`browser\` for the ESM entry path. Either remove \`wippy.path\` or switch to wippyPagePlugin().`);
113
+ }
114
+
115
+ // src/page-plugin.ts
116
+ function wippyPagePlugin(options = {}) {
10
117
  let root;
118
+ let resolvedSerialized = null;
119
+ function compute() {
120
+ if (resolvedSerialized !== null)
121
+ return resolvedSerialized;
122
+ const pkgPath = options.packageJsonPath ?? resolve2(root, "package.json");
123
+ let loaded;
124
+ try {
125
+ loaded = loadPackage(pkgPath);
126
+ } catch (err) {
127
+ if (options.required)
128
+ throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${err.message}`);
129
+ return null;
130
+ }
131
+ validatePagePackage(loaded.pkg, pkgPath);
132
+ const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {
133
+ throw new Error(msg);
134
+ });
135
+ resolvedSerialized = JSON.stringify(resolvedPkg);
136
+ return resolvedSerialized;
137
+ }
11
138
  return {
12
- name: "@wippy-fe/vite-plugin:wippy-package",
139
+ name: "@wippy-fe/vite-plugin:wippy-page",
13
140
  enforce: "pre",
14
141
  configResolved(config) {
15
142
  root = config.root;
143
+ resolvedSerialized = null;
16
144
  },
17
145
  transformIndexHtml: {
18
146
  order: "pre",
19
147
  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}`);
148
+ const json = compute();
149
+ if (json === null)
27
150
  return html;
28
- }
29
- const safeJson = raw.replace(/<\/script/gi, "<\\/script");
151
+ const safeJson = defangScriptClose(json);
30
152
  const tag = `<script type="application/json" data-role="${WIPPY_PACKAGE_DATA_ROLE}">${safeJson}</script>`;
31
153
  if (html.includes("<head>"))
32
154
  return html.replace("<head>", `<head>
33
155
  ${tag}`);
34
156
  return tag + html;
35
157
  }
158
+ },
159
+ generateBundle() {
160
+ const json = compute();
161
+ if (json === null)
162
+ return;
163
+ this.emitFile({
164
+ type: "asset",
165
+ fileName: "wippy-meta.json",
166
+ source: json
167
+ });
168
+ }
169
+ };
170
+ }
171
+
172
+ // src/component-plugin.ts
173
+ import { resolve as resolve3 } from "path";
174
+ function wippyComponentPlugin(options = {}) {
175
+ let root;
176
+ return {
177
+ name: "@wippy-fe/vite-plugin:wippy-component",
178
+ enforce: "pre",
179
+ configResolved(config) {
180
+ root = config.root;
181
+ },
182
+ generateBundle() {
183
+ const pkgPath = options.packageJsonPath ?? resolve3(root, "package.json");
184
+ let loaded;
185
+ try {
186
+ loaded = loadPackage(pkgPath);
187
+ } catch (err) {
188
+ if (options.required)
189
+ throw new Error(`[@wippy-fe/vite-plugin] failed to read ${pkgPath}: ${err.message}`);
190
+ return;
191
+ }
192
+ validateComponentPackage(loaded.pkg, pkgPath);
193
+ const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {
194
+ throw new Error(msg);
195
+ });
196
+ this.emitFile({
197
+ type: "asset",
198
+ fileName: "wippy-meta.json",
199
+ source: JSON.stringify(resolvedPkg)
200
+ });
36
201
  }
37
202
  };
38
203
  }
204
+
205
+ // src/index.ts
206
+ function wippyPackagePlugin(...args) {
207
+ return wippyPagePlugin(...args);
208
+ }
39
209
  export {
40
210
  WIPPY_PACKAGE_DATA_ROLE,
41
- wippyPackagePlugin
211
+ validateComponentPackage,
212
+ validatePagePackage,
213
+ wippyComponentPlugin,
214
+ wippyPackagePlugin,
215
+ wippyPagePlugin
42
216
  };
43
217
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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":[]}
1
+ {"version":3,"sources":["../../@wippy-fe--shared/src/index.ts","../src/page-plugin.ts","../src/shared.ts","../src/component-plugin.ts","../src/index.ts"],"sourcesContent":["/**\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","/**\n * `wippyPagePlugin` — for `view.page` packages (those with `wippy.type === 'page'`).\n *\n * Two outputs from a single resolved JSON source:\n * 1. An inline `<script type=\"application/json\" data-role=\"@wippy/package\">`\n * injected at the top of `app.html`'s `<head>`. Read at boot by\n * host-less dev-proxy.\n * 2. `wippy-meta.json` emitted into Vite's `outDir`. Read at request time\n * by `wippy/views` in wippy-hosted mode.\n *\n * Both contain the consumer's `package.json` with every `file://<relative>`\n * reference replaced by its file contents. Single source = identical render\n * in either mode.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { LoadedPackage } from './shared'\nimport { resolve } from 'node:path'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\nimport { defangScriptClose, loadPackage, resolveFileRefs, validatePagePackage } from './shared'\n\nexport interface WippyPagePluginOptions {\n /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` is missing. If `false`\n * (default), the plugin silently no-ops — useful for shared configs\n * where some target apps don't need the injection.\n */\n required?: boolean\n}\n\nexport function wippyPagePlugin(options: WippyPagePluginOptions = {}): Plugin {\n let root: string\n // Cache so transformIndexHtml + generateBundle don't re-read package.json\n // and don't double-resolve file:// refs within the same build.\n let resolvedSerialized: string | null = null\n\n function compute(): string | null {\n if (resolvedSerialized !== null)\n return resolvedSerialized\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let loaded: LoadedPackage\n try {\n loaded = loadPackage(pkgPath)\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 null\n }\n // Strict spec validation — throws on any wippy-block shape violation\n // (wrong `wippy.type`, missing `wippy.path`, forbidden `wippy.tagName`,\n // etc.). We throw rather than warn so misconfigurations fail the build\n // instead of silently shipping broken metadata. See shared.ts for the\n // full rule set.\n validatePagePackage(loaded.pkg, pkgPath)\n\n // `file://` basename convention violations also throw: the\n // `.do-not-link.` infix is the AI-readable directive that prevents\n // agents from `<link>`-ing the file directly. A consumer using\n // `file://theme.css` defeats that signal even though the resolved\n // content is correct, so we fail the build to force the rename.\n const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {\n throw new Error(msg)\n })\n resolvedSerialized = JSON.stringify(resolvedPkg)\n return resolvedSerialized\n }\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-page',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n resolvedSerialized = null\n },\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n const json = compute()\n if (json === null)\n return html\n const safeJson = defangScriptClose(json)\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 return tag + html\n },\n },\n generateBundle() {\n const json = compute()\n if (json === null)\n return\n this.emitFile({\n type: 'asset',\n fileName: 'wippy-meta.json',\n source: json,\n })\n },\n }\n}\n","/**\n * Shared helpers for `wippyPagePlugin` and `wippyComponentPlugin`.\n *\n * Both plugins produce `dist/wippy-meta.json` — a resolved copy of the\n * consumer's `package.json` with every `file://<relative-path>` reference\n * replaced by the referenced file's UTF-8 contents. The page plugin\n * additionally injects the same resolved JSON into `app.html` as an\n * `<script type=\"application/json\" data-role=\"@wippy/package\">` so host-less\n * mode (gen-2-chat dev-proxy) can read it synchronously at boot.\n *\n * `wippy-meta.json` is the MANDATORY contract between consumers and\n * `wippy/views`. See `web_components.spec.md` § \"Package metadata: source of\n * truth\" for the full spec.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { basename, dirname, resolve } from 'node:path'\n\nexport interface LoadedPackage {\n /** The parsed `package.json` as a JS object. */\n pkg: Record<string, unknown>\n /**\n * Absolute path to the directory containing `package.json` — used as the\n * base for `file://` reference resolution.\n */\n baseDir: string\n}\n\n/**\n * Read + parse `package.json` from disk. Throws if the file is missing or\n * not valid JSON — the build is expected to fail loud rather than silently\n * skip the package.\n */\nexport function loadPackage(packageJsonPath: string): LoadedPackage {\n const raw = readFileSync(packageJsonPath, 'utf-8')\n const pkg = JSON.parse(raw) as Record<string, unknown>\n return { pkg, baseDir: dirname(packageJsonPath) }\n}\n\n/**\n * camelCase → kebab-case conversion used by the `*.do-not-link.<ext>`\n * naming convention. Treats consecutive capitals as a single word so\n * `customCSS` → `custom-css` (not `custom-c-s-s`). Matches lodash's\n * kebabCase semantics for these specific input shapes.\n *\n * Examples codified in tests:\n * - `customCSS` → `custom-css`\n * - `customCss` → `custom-css`\n * - `cssVariables` → `css-variables`\n * - `tailwindConfig` → `tailwind-config`\n * - `description` → `description`\n * - `URLPath` → `url-path`\n */\nexport function kebabize(camel: string): string {\n return camel\n // Insert a hyphen between a run of capitals and a following Capital+lowercase:\n // \"URLPath\" → \"URL-Path\"\n // \"customCSS\" stays \"customCSS\" here (no following lowercase)\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')\n // Insert a hyphen between a lowercase/digit and a following capital:\n // \"customCSS\" → \"custom-CSS\"\n // \"URL-Path\" → \"URL-Path\" (unchanged — already split)\n .replace(/([a-z\\d])([A-Z])/g, '$1-$2')\n .toLowerCase()\n}\n\n/**\n * Validator used by `resolveFileRefs` to surface mismatches between a\n * `file://` reference's basename and the canonical `<field-kebab>.do-not-link.<ext>`\n * convention. Returns the expected basename (for use in the warning\n * message) or `null` if the actual basename already conforms.\n *\n * Convention: basename without extension MUST end with `.do-not-link` and\n * its prefix MUST equal `kebabize(fieldName)`. Examples:\n * - field `customCSS`, file `custom-css.do-not-link.css` → null (ok)\n * - field `customCSS`, file `my-theme.css` → \"custom-css.do-not-link.css\"\n * - field `customCSS`, file `custom-css.css` → \"custom-css.do-not-link.css\"\n *\n * `null` field name (top-level / array index) skips the check.\n */\nexport function expectedDoNotLinkBasename(fieldName: string | null, actualBasename: string): string | null {\n if (!fieldName)\n return null\n const dot = actualBasename.lastIndexOf('.')\n if (dot <= 0)\n return `${kebabize(fieldName)}.do-not-link.<ext>`\n const ext = actualBasename.slice(dot + 1)\n const expected = `${kebabize(fieldName)}.do-not-link.${ext}`\n if (actualBasename === expected)\n return null\n return expected\n}\n\n/**\n * Recursively replace every string value of shape `file://<relative-path>`\n * with the referenced file's UTF-8 contents. Paths are resolved against\n * `baseDir` (typically the directory containing `package.json`).\n *\n * Non-string, non-array, non-object values pass through unchanged. Missing\n * files throw — the convention is that `file://` refs are load-bearing.\n *\n * `onWarn` (optional) is called when a `file://` basename doesn't match the\n * canonical `<field-kebab>.do-not-link.<ext>` convention. The warning is\n * advisory — resolution still succeeds — so consumers can opt out by not\n * passing the callback.\n */\nexport function resolveFileRefs<T>(\n node: T,\n baseDir: string,\n onWarn?: (msg: string) => void,\n fieldName: string | null = null,\n): T {\n if (typeof node === 'string' && node.startsWith('file://')) {\n const rel = node.slice('file://'.length)\n const abs = resolve(baseDir, rel)\n if (onWarn) {\n const expected = expectedDoNotLinkBasename(fieldName, basename(rel))\n if (expected) {\n onWarn(`[@wippy-fe/vite-plugin] file:// basename \"${basename(rel)}\" for field \"${fieldName}\" does not match the *.do-not-link.<ext> convention. Expected: \"${expected}\". See web_components.spec.md § \"Package metadata: source of truth\".`)\n }\n }\n return readFileSync(abs, 'utf-8') as unknown as T\n }\n if (Array.isArray(node))\n return node.map(v => resolveFileRefs(v, baseDir, onWarn, fieldName)) as unknown as T\n if (node && typeof node === 'object') {\n return Object.fromEntries(\n Object.entries(node as Record<string, unknown>)\n .map(([k, v]) => [k, resolveFileRefs(v, baseDir, onWarn, k)]),\n ) as unknown as T\n }\n return node\n}\n\n/**\n * Defang `</script>` sequences inside a JSON string so it's safe to embed\n * verbatim inside an inline `<script type=\"application/json\">` tag. Case\n * insensitive (`</SCRIPT`, `</Script`, etc. all need escaping).\n */\nexport function defangScriptClose(json: string): string {\n return json.replace(/<\\/script/gi, '<\\\\/script')\n}\n\n/**\n * Validate a consumer's `package.json` against the `wippy-component-1.0`\n * spec FOR A PAGE PACKAGE. Throws with a clear, actionable message on any\n * violation. Called by `wippyPagePlugin` at build time so misconfigurations\n * fail the build instead of silently shipping broken metadata.\n *\n * We throw rather than warn because nobody reads build warnings — they\n * scroll past in CI output and get filtered out of IDE diagnostics. A\n * field-shape mistake that warns today is a real bug tomorrow.\n *\n * Rules enforced (must all hold for a page):\n * - Top-level `name` and `version` must be strings.\n * - `wippy` must be a plain object.\n * - `wippy.type`, when set, MUST equal `'page'` (not `'component'`,\n * `'widget'`, or anything else).\n * - `wippy.path` is REQUIRED for pages (HTML entry path).\n * - `wippy.tagName` is FORBIDDEN for pages (custom-element-tag is a\n * component-only concept).\n * - `specification`, when set, MUST equal `'wippy-component-1.0'`.\n */\nexport function validatePagePackage(pkg: Record<string, unknown>, packageJsonPath: string): void {\n const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`\n\n if (typeof pkg.name !== 'string' || pkg.name.length === 0)\n throw new Error(`${prefix} \\`name\\` is required and must be a non-empty string`)\n if (typeof pkg.version !== 'string' || pkg.version.length === 0)\n throw new Error(`${prefix} \\`version\\` is required and must be a non-empty string`)\n if (pkg.specification !== undefined && pkg.specification !== 'wippy-component-1.0')\n throw new Error(`${prefix} \\`specification\\` must be \"wippy-component-1.0\" (got ${JSON.stringify(pkg.specification)})`)\n\n const wippy = pkg.wippy\n if (wippy === undefined || wippy === null)\n throw new Error(`${prefix} top-level \\`wippy\\` block is required for a page package using wippyPagePlugin()`)\n if (typeof wippy !== 'object' || Array.isArray(wippy))\n throw new Error(`${prefix} \\`wippy\\` must be a JSON object (got ${Array.isArray(wippy) ? 'array' : typeof wippy})`)\n\n const w = wippy as Record<string, unknown>\n\n if (w.type !== undefined && w.type !== 'page')\n throw new Error(`${prefix} \\`wippy.type\\` is \"${String(w.type)}\" but wippyPagePlugin() was used. Either set \\`wippy.type: \"page\"\\` or switch to wippyComponentPlugin() for components/widgets.`)\n\n if (w.path === undefined)\n throw new Error(`${prefix} \\`wippy.path\\` is required for a page package (the relative path to the HTML entry, e.g. \"dist/app.html\")`)\n if (typeof w.path !== 'string' || w.path.length === 0)\n throw new Error(`${prefix} \\`wippy.path\\` must be a non-empty string (got ${JSON.stringify(w.path)})`)\n\n if (w.tagName !== undefined)\n throw new Error(`${prefix} \\`wippy.tagName\\` is set on a page package but the field is component-only. Either remove it or switch to wippyComponentPlugin().`)\n}\n\n/**\n * Validate a consumer's `package.json` for a component/widget package using\n * `wippyComponentPlugin`. Throws on any violation. Same rationale as\n * `validatePagePackage` — fail loud at build time.\n *\n * Rules enforced (must all hold for a component):\n * - Top-level `name` and `version` must be strings.\n * - `wippy` must be a plain object.\n * - `wippy.type`, when set, MUST be one of `'component'` or `'widget'` (the\n * spec uses both interchangeably for ESM-shipped custom elements).\n * - `wippy.tagName` is REQUIRED for components — without it the host has\n * no element name to register against.\n * - `wippy.path` is FORBIDDEN for components (HTML-entry is a page-only\n * concept; components use top-level `browser` for the ESM entry).\n * - `specification`, when set, MUST equal `'wippy-component-1.0'`.\n * - `browser` SHOULD be set (the ESM entry path); only warn-by-throw if\n * absent because the plugin still emits useful meta without it.\n */\nexport function validateComponentPackage(pkg: Record<string, unknown>, packageJsonPath: string): void {\n const prefix = `[@wippy-fe/vite-plugin] ${packageJsonPath}:`\n\n if (typeof pkg.name !== 'string' || pkg.name.length === 0)\n throw new Error(`${prefix} \\`name\\` is required and must be a non-empty string`)\n if (typeof pkg.version !== 'string' || pkg.version.length === 0)\n throw new Error(`${prefix} \\`version\\` is required and must be a non-empty string`)\n if (pkg.specification !== undefined && pkg.specification !== 'wippy-component-1.0')\n throw new Error(`${prefix} \\`specification\\` must be \"wippy-component-1.0\" (got ${JSON.stringify(pkg.specification)})`)\n\n const wippy = pkg.wippy\n if (wippy === undefined || wippy === null)\n throw new Error(`${prefix} top-level \\`wippy\\` block is required for a component package using wippyComponentPlugin()`)\n if (typeof wippy !== 'object' || Array.isArray(wippy))\n throw new Error(`${prefix} \\`wippy\\` must be a JSON object (got ${Array.isArray(wippy) ? 'array' : typeof wippy})`)\n\n const w = wippy as Record<string, unknown>\n\n if (w.type !== undefined && w.type !== 'component' && w.type !== 'widget')\n throw new Error(`${prefix} \\`wippy.type\\` is \"${String(w.type)}\" but wippyComponentPlugin() was used. Either set \\`wippy.type: \"component\"\\` (or \"widget\") or switch to wippyPagePlugin() for pages.`)\n\n if (w.tagName === undefined)\n throw new Error(`${prefix} \\`wippy.tagName\\` is required for a component package (the custom-element tag name, e.g. \"example-mermaid\"). Without it the host cannot register the element.`)\n if (typeof w.tagName !== 'string' || w.tagName.length === 0)\n throw new Error(`${prefix} \\`wippy.tagName\\` must be a non-empty string (got ${JSON.stringify(w.tagName)})`)\n // Custom Elements spec (PotentialCustomElementName):\n // - MUST start with an ASCII lowercase letter [a-z]\n // - MUST contain a hyphen\n // - MAY contain ASCII lowercase letters, digits, or hyphens\n // (The full spec also allows ., _, and some Unicode ranges, but in practice\n // Wippy tag names only ever use [a-z0-9-]. We keep the check tight rather\n // than full-spec because a \"FOO-BAR\" or \"-leading\" passing validation today\n // would silently break tomorrow when `customElements.define()` rejects it.)\n //\n // Mirror twin: `@wippy-fe/proxy`'s `vue-warn-suppressor.ts` uses an\n // equivalent regex (`/^[a-z][a-z0-9]*-[a-z0-9-]*$/`) to decide whether\n // an unresolved Vue tag \"looks like a pending-registration custom\n // element\". The two patterns match exactly the same name shape — keep\n // them in sync. Functionally identical; differ only in character-class\n // notation (`[\\da-z]` vs `[a-z0-9]`).\n if (!/^[a-z][\\da-z]*-[-\\da-z]*$/.test(w.tagName))\n throw new Error(`${prefix} \\`wippy.tagName\\` \"${w.tagName}\" is not a valid Custom Elements name. Must start with an ASCII lowercase letter, contain a hyphen, and use only lowercase letters, digits, and hyphens. Examples: \"example-mermaid\", \"my-widget-v2\".`)\n // Reserved names that browsers refuse to register (HTML5 spec):\n const reserved = new Set([\n 'annotation-xml',\n 'color-profile',\n 'font-face',\n 'font-face-src',\n 'font-face-uri',\n 'font-face-format',\n 'font-face-name',\n 'missing-glyph',\n ])\n if (reserved.has(w.tagName))\n throw new Error(`${prefix} \\`wippy.tagName\\` \"${w.tagName}\" is a reserved HTML name; browsers will refuse to register it.`)\n\n if (w.path !== undefined)\n throw new Error(`${prefix} \\`wippy.path\\` is set on a component package but the field is page-only. Components use top-level \\`browser\\` for the ESM entry path. Either remove \\`wippy.path\\` or switch to wippyPagePlugin().`)\n}\n","/**\n * `wippyComponentPlugin` — for `view.component` packages (those with\n * `wippy.type === 'component'`).\n *\n * Components ship a JS module, not an HTML entry — there's nothing to inject\n * an inline `<script>` into. The plugin's only job is to emit `wippy-meta.json`\n * into Vite's `outDir` for `wippy/views` to read at request time.\n *\n * The `wippy-meta.json` file is the MANDATORY contract between components and\n * `wippy/views`. See `web_components.spec.md` § \"Package metadata: source of\n * truth\". Without it, `views` falls back to its legacy YAML-synthesis path,\n * which is deprecated and exists only for migration.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { LoadedPackage } from './shared'\nimport { resolve } from 'node:path'\nimport { loadPackage, resolveFileRefs, validateComponentPackage } from './shared'\n\nexport interface WippyComponentPluginOptions {\n /** Override the `package.json` path. Defaults to `<viteRoot>/package.json`. */\n packageJsonPath?: string\n /**\n * If `true`, fail the build when `package.json` is missing. If `false`\n * (default), the plugin silently no-ops.\n */\n required?: boolean\n}\n\nexport function wippyComponentPlugin(options: WippyComponentPluginOptions = {}): Plugin {\n let root: string\n\n return {\n name: '@wippy-fe/vite-plugin:wippy-component',\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n root = config.root\n },\n generateBundle() {\n const pkgPath = options.packageJsonPath ?? resolve(root, 'package.json')\n let loaded: LoadedPackage\n try {\n loaded = loadPackage(pkgPath)\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\n }\n // Strict spec validation — throws on any wippy-block shape violation\n // (wrong `wippy.type`, missing `wippy.tagName`, forbidden `wippy.path`,\n // tagName without hyphen, etc.). We throw rather than warn so\n // misconfigurations fail the build instead of silently shipping broken\n // metadata. See shared.ts for the full rule set.\n validateComponentPackage(loaded.pkg, pkgPath)\n\n // `file://` basename convention violations throw — see page-plugin's\n // identical comment for the rationale.\n const resolvedPkg = resolveFileRefs(loaded.pkg, loaded.baseDir, (msg) => {\n throw new Error(msg)\n })\n this.emitFile({\n type: 'asset',\n fileName: 'wippy-meta.json',\n source: JSON.stringify(resolvedPkg),\n })\n },\n }\n}\n","/**\n * Vite plugins for Wippy view.page and view.component packages.\n *\n * Both plugins emit `dist/wippy-meta.json` — the consumer's `package.json`\n * with every `file://<relative-path>` reference replaced by the referenced\n * file's UTF-8 contents. `wippy-meta.json` is the MANDATORY contract between\n * consumers and `wippy/views` ≥ 1.0.31. See `web_components.spec.md` §\n * \"Package metadata: source of truth\" for the full spec.\n *\n * - `wippyPagePlugin()` — for `view.page` packages. Emits `wippy-meta.json`\n * AND injects an inline `<script data-role=\"@wippy/package\">` into the\n * built `app.html` (for host-less mode).\n * - `wippyComponentPlugin()` — for `view.component` packages. Emits\n * `wippy-meta.json` only; components have no HTML entry.\n *\n * Convention: bulky string fields (`customCSS`, `description`, etc.) should\n * be moved into separate files referenced via `file://` and named\n * `<field-name-kebab>.do-not-link.<ext>` — basename matches the kebab-case\n * wippy block field name (e.g. `customCSS` → `custom-css.do-not-link.css`,\n * `description` → `description.do-not-link.md`). The `.do-not-link.` infix\n * is what stops AI agents from `<link>`-ing the file directly into app.html.\n * The proxy injects the content at runtime from the resolved package metadata.\n *\n * **Status: Draft 1 (preview).** API may change between minor releases.\n */\n\nimport type { Plugin } from 'vite'\nimport { WIPPY_PACKAGE_DATA_ROLE } from '@wippy-fe/shared'\nimport { wippyPagePlugin } from './page-plugin'\n\nexport { WIPPY_PACKAGE_DATA_ROLE }\nexport { wippyComponentPlugin } from './component-plugin'\nexport type { WippyComponentPluginOptions } from './component-plugin'\nexport { wippyPagePlugin } from './page-plugin'\nexport type { WippyPagePluginOptions } from './page-plugin'\nexport type { WippyPagePluginOptions as WippyPackagePluginOptions } from './page-plugin'\n\n/**\n * @deprecated Use `wippyPagePlugin()` instead. The new name disambiguates\n * the page-side plugin from `wippyComponentPlugin()` introduced alongside it.\n * This alias preserves backwards compatibility for existing `vite.config.ts`\n * files and will be removed in a future major.\n */\nexport function wippyPackagePlugin(...args: Parameters<typeof wippyPagePlugin>): Plugin {\n return wippyPagePlugin(...args)\n}\n// Re-export the strict validators so CI tooling / external linters can\n// reuse the same rules the plugins enforce at build time.\nexport { validateComponentPackage, validatePagePackage } from './shared'\n"],"mappings":"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","names":["resolve","resolve","resolve","resolve"]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@wippy-fe/vite-plugin",
3
3
  "type": "module",
4
- "version": "0.0.31",
5
- "description": "Vite plugins for Wippy apps and web components — package metadata injection, future build-time helpers.",
4
+ "version": "0.0.34",
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
8
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "vite": "^5 || ^6 || ^7"
17
17
  },
18
18
  "devDependencies": {
19
- "@wippy-fe/shared": "0.0.31"
19
+ "@wippy-fe/shared": "0.0.34"
20
20
  },
21
21
  "scripts": {
22
22
  "build": "tsup"