@persona/layout-editor-remote-module 0.0.0-bootstrap.0 → 0.5.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.
@@ -0,0 +1,435 @@
1
+ import { n as DEFAULT_MANIFEST_PATH, r as defineLayoutModule, t as remoteModuleRegistrationManifestV1Schema } from "./schema-BeI8GdXS.mjs";
2
+ import { z } from "zod";
3
+ import { build } from "esbuild";
4
+ import { existsSync } from "node:fs";
5
+ import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { build as build$1 } from "vite";
8
+ import { tmpdir } from "node:os";
9
+
10
+ //#region src/remote-shims.ts
11
+ /**
12
+ * Specifiers a remote module bundle must NOT carry its own copy of — each is
13
+ * redirected (via esbuild/Vite `alias`) to a tiny CJS shim that reads the
14
+ * host's own instance off `globalThis.__PERSONA_REMOTE_MODULE_DEPS__` instead
15
+ * of bundling the real package.
16
+ *
17
+ * Why this exists: hooks require the calling code's React module instance to
18
+ * be the exact one actively rendering the tree. A remote module built with
19
+ * its own bundled `react` (the default, before this) creates a SECOND,
20
+ * unrelated React instance — any hook call from code using that instance
21
+ * (including styled-components' own `useContext` inside `ThemeProvider`, and
22
+ * anything in `@persona/design-system`, which itself just imports `react`)
23
+ * reads a dispatcher that was never set, producing exactly
24
+ * "Cannot read properties of null (reading 'useContext')". Sharing the
25
+ * instance via a runtime global — rather than bundling it — fixes this for
26
+ * every remote module, not just one that happens to avoid hooks.
27
+ *
28
+ * `styled-components` needs the same treatment for a subtler reason: even
29
+ * with a shared React, a SEPARATE `styled-components` package instance would
30
+ * call `React.createContext()` itself and produce a DIFFERENT Context object
31
+ * than the host's — a `<ThemeProvider>` from one instance is invisible to
32
+ * `styled.div` definitions from the other, no matter how the React inside
33
+ * them is shared.
34
+ */
35
+ /**
36
+ * Order matters: Vite's alias resolution (via its wrapped
37
+ * `@rollup/plugin-alias`) treats a string `find` as a PREFIX match, not just
38
+ * exact — `importee === find || importee.startsWith(find + '/')` — and takes
39
+ * the FIRST entry (in object key / array order) that matches. Since
40
+ * `'react/jsx-runtime'.startsWith('react/')` is true, listing `'react'`
41
+ * before `'react/jsx-runtime'` would make every `react/jsx-runtime` import
42
+ * resolve against the 'react' shim's path (e.g.
43
+ * `.../react.cjs/jsx-runtime`, ENOTDIR) instead of its own shim. The more
44
+ * specific specifier must come first.
45
+ */
46
+ const SHIMMABLE_SPECIFIERS = [
47
+ "react/jsx-runtime",
48
+ "react/jsx-dev-runtime",
49
+ "react",
50
+ "react-dom",
51
+ "styled-components"
52
+ ];
53
+ const HOST_DEPS_GLOBAL = "__PERSONA_REMOTE_MODULE_DEPS__";
54
+ /**
55
+ * `module.exports = ...` (not a static ESM re-export) deliberately — an ESM
56
+ * `export` list has to be known at author time, but the shimmed value's
57
+ * actual shape lives on a runtime global. esbuild's own CJS interop
58
+ * (`__toESM`) forwards named-import access to a CJS module's properties
59
+ * dynamically, at RUNTIME, regardless of what the bundler could see
60
+ * statically — the same mechanism that already makes `import { useState }
61
+ * from 'react'` work today, since React itself ships as CJS.
62
+ *
63
+ * Consuming apps with `esModuleInterop` off in tsconfig (allowed by TS —
64
+ * `allowSyntheticDefaultImports` permits the import *syntax* without it) get
65
+ * esbuild's "Node mode" CJS interop for a DEFAULT import of an aliased CJS
66
+ * file: `target.default = mod` (the whole `module.exports`), unconditionally
67
+ * — `mod.__esModule` is NOT consulted in that mode, so it can't be used to
68
+ * steer this. That's a correct model for a module whose "the module" and "the
69
+ * default export" are the same thing (true for `react`/`react-dom`, which
70
+ * have no own `.default`). It's wrong for `styled-components`: its
71
+ * module.exports has BOTH a whole-module shape (ThemeProvider, css, ... as
72
+ * plain properties) AND a separate own `.default` (the callable `styled` tag
73
+ * function) — Node mode's `target.default = mod` picks the whole-module
74
+ * shape, so `styled.div`/`.attrs(...)` throws on `undefined`.
75
+ *
76
+ * The fix: make OUR module.exports itself resolve the ambiguity — if the
77
+ * host's real module has its own `.default` distinct from itself, export
78
+ * THAT (with the rest of the module's named properties copied onto it, so
79
+ * `styled.ThemeProvider`-style access — not used by styled-components itself,
80
+ * but keeps this general — and named imports both still resolve). Mutating
81
+ * the host's real default value (rather than building a fresh wrapper) is
82
+ * deliberate: styled-components' internals sometimes compare a component
83
+ * against the exact `styled` function reference (e.g. `isStyledComponent`);
84
+ * a copy would break that. Ordinary functions/objects aren't frozen, so this
85
+ * is safe for every specifier actually on this list.
86
+ */
87
+ function shimModuleSource(specifier) {
88
+ const globalAccess = `globalThis.${HOST_DEPS_GLOBAL} && globalThis.${HOST_DEPS_GLOBAL}[${JSON.stringify(specifier)}]`;
89
+ const missingMessage = `[persona-layout-modules] the host did not expose "${specifier}" via globalThis.${HOST_DEPS_GLOBAL} before this module loaded`;
90
+ return [
91
+ `var __persona_dep = ${globalAccess} || (() => { throw new Error(${JSON.stringify(missingMessage)}); })();`,
92
+ `var __persona_base = (__persona_dep && 'default' in __persona_dep && __persona_dep.default !== __persona_dep) ? __persona_dep.default : __persona_dep;`,
93
+ `module.exports = Object.assign(__persona_base, __persona_dep, { __esModule: true, default: __persona_base });`,
94
+ ""
95
+ ].join("\n");
96
+ }
97
+ let cachedShimPaths;
98
+ /**
99
+ * Writes one `.cjs` shim file per specifier to a temp dir (once per process
100
+ * — the content is static, not per-request) and returns a specifier -> path
101
+ * map suitable for esbuild's/Vite's `alias` option. `.cjs` is deliberate: it
102
+ * forces CommonJS treatment regardless of any ambient `package.json#type`.
103
+ */
104
+ function ensureRemoteShimFiles() {
105
+ if (!cachedShimPaths) cachedShimPaths = (async () => {
106
+ const dir = await mkdtemp(path.join(tmpdir(), "persona-layout-module-shims-"));
107
+ const entries = await Promise.all(SHIMMABLE_SPECIFIERS.map(async (specifier) => {
108
+ const fileName = `${specifier.replace(/[\\/]/g, "_")}.cjs`;
109
+ const filePath = path.join(dir, fileName);
110
+ await writeFile(filePath, shimModuleSource(specifier), "utf8");
111
+ return [specifier, filePath];
112
+ }));
113
+ return Object.fromEntries(entries);
114
+ })();
115
+ return cachedShimPaths;
116
+ }
117
+
118
+ //#endregion
119
+ //#region src/vite-plugin.ts
120
+ /**
121
+ * Vite plugin that takes a statically-declared list of layout modules
122
+ * (`options.modules`, authored via `defineLayoutModule` — see ./define.ts),
123
+ * wires every `code`-delivered entry into the build as an addressable ESM
124
+ * chunk, ensures every `document`-delivered (`.html`) entry is actually
125
+ * built/served, and emits a `RemoteModuleRegistrationManifestV1[]` document
126
+ * persona-web can intake.
127
+ *
128
+ * Modules are configured directly here, not discovered by globbing the
129
+ * source tree — a module IS whatever's in `options.modules`, full stop.
130
+ */
131
+ function personaLayoutModules(options) {
132
+ const manifestPath = (options.manifestPath ?? DEFAULT_MANIFEST_PATH).replace(/^\/+/, "");
133
+ let root = process.cwd();
134
+ let resolvedConfig;
135
+ let definitions = [];
136
+ return {
137
+ async closeBundle() {
138
+ if (definitions.length === 0 || resolvedConfig.command !== "build") return;
139
+ const outDir = path.isAbsolute(resolvedConfig.build.outDir) ? resolvedConfig.build.outDir : path.resolve(root, resolvedConfig.build.outDir);
140
+ const input = {};
141
+ for (const definition of definitions) if (definition.render.kind === "code") input[entryNameFor(definition)] = definition.render.absolutePath;
142
+ let emitted = [];
143
+ if (Object.keys(input).length > 0) {
144
+ const shimAlias = await ensureRemoteShimFiles();
145
+ const result = await build$1({
146
+ base: resolvedConfig.base,
147
+ build: {
148
+ emptyOutDir: false,
149
+ outDir,
150
+ rollupOptions: {
151
+ input,
152
+ preserveEntrySignatures: "exports-only"
153
+ },
154
+ write: true
155
+ },
156
+ configFile: false,
157
+ esbuild: { jsx: "automatic" },
158
+ logLevel: "warn",
159
+ mode: "production",
160
+ plugins: options.modulePlugins?.() ?? [],
161
+ resolve: { alias: {
162
+ ...resolveAliasForEsbuild(resolvedConfig),
163
+ ...shimAlias
164
+ } },
165
+ root
166
+ });
167
+ emitted = (Array.isArray(result) ? result : [result]).flatMap((output) => "output" in output ? output.output : []);
168
+ }
169
+ const base = resolvedConfig.base ?? "/";
170
+ const manifests = [];
171
+ for (const definition of definitions) {
172
+ const render = resolveManifestDelivery(definition, definition.render, emitted, root, base);
173
+ const css = /* @__PURE__ */ new Set();
174
+ if (render.chunk) collectCss(render.chunk, css);
175
+ const manifest = {
176
+ ...buildManifestBase(definition),
177
+ remote: {
178
+ css: css.size > 0 ? [...css].map((fileName) => joinUrl(base, fileName)) : void 0,
179
+ render: render.value
180
+ }
181
+ };
182
+ const result2 = remoteModuleRegistrationManifestV1Schema.safeParse(manifest);
183
+ if (!result2.success) throw new Error(`[persona-layout-modules] ${definition.definitionFilePath}: invalid manifest for "${definition.input.key}":\n${z.prettifyError(result2.error)}`);
184
+ manifests.push(manifest);
185
+ }
186
+ assertUniqueKeys(manifests);
187
+ const manifestFilePath = path.join(outDir, manifestPath);
188
+ await mkdir(path.dirname(manifestFilePath), { recursive: true });
189
+ await writeFile(manifestFilePath, JSON.stringify({ modules: manifests }, null, 2), "utf8");
190
+ },
191
+ async config(config, env) {
192
+ root = config.root ? path.resolve(config.root) : process.cwd();
193
+ definitions = options.modules.map((input) => ({
194
+ definitionFilePath: input.key,
195
+ input,
196
+ render: resolveEntry(root, input.render)
197
+ }));
198
+ if (env.command !== "build") return;
199
+ const existingInputs = absoluteInputPaths(config, root);
200
+ const toInject = {};
201
+ for (const definition of definitions) {
202
+ const delivery = definition.render;
203
+ if (delivery.kind === "document" && !existingInputs.has(delivery.absolutePath)) {
204
+ toInject[entryNameFor(definition)] = delivery.absolutePath;
205
+ existingInputs.add(delivery.absolutePath);
206
+ }
207
+ }
208
+ if (Object.keys(toInject).length > 0) {
209
+ config.build ??= {};
210
+ config.build.rollupOptions ??= {};
211
+ config.build.rollupOptions.input = {
212
+ ...toInputRecord(config.build.rollupOptions.input, root),
213
+ ...toInject
214
+ };
215
+ }
216
+ },
217
+ configResolved(config) {
218
+ resolvedConfig = config;
219
+ for (const definition of definitions) checkDeliveryExists(definition, definition.render);
220
+ },
221
+ configureServer(server) {
222
+ server.middlewares.use(async (req, res, next) => {
223
+ if (req.url === `/${manifestPath}`) {
224
+ const base = resolvedConfig.base ?? "/";
225
+ const devManifests = definitions.map((definition$1) => ({
226
+ ...buildManifestBase(definition$1),
227
+ remote: { render: devManifestDelivery(root, base, definition$1, definition$1.render) }
228
+ }));
229
+ res.setHeader("Content-Type", "application/json");
230
+ res.setHeader("Access-Control-Allow-Origin", "*");
231
+ res.end(JSON.stringify({ modules: devManifests }, null, 2));
232
+ return;
233
+ }
234
+ const bundleTarget = matchDevBundleRequest(req.url, definitions);
235
+ if (!bundleTarget) {
236
+ next();
237
+ return;
238
+ }
239
+ const { absolutePath, definition } = bundleTarget;
240
+ try {
241
+ const code = await bundleForDev(absolutePath, root, resolvedConfig);
242
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
243
+ res.setHeader("Access-Control-Allow-Origin", "*");
244
+ res.setHeader("Cache-Control", "no-store");
245
+ res.end(code);
246
+ } catch (error) {
247
+ server.config.logger.error(`[persona-layout-modules] failed to dev-bundle ${definition.definitionFilePath}: ${String(error)}`);
248
+ res.statusCode = 500;
249
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
250
+ res.end(`[persona-layout-modules] failed to bundle: ${String(error)}`);
251
+ }
252
+ });
253
+ },
254
+ name: "persona-layout-modules"
255
+ };
256
+ }
257
+ const DEV_BUNDLE_PREFIX = "/@persona-layout-module";
258
+ /**
259
+ * Every absolute path Vite is ALREADY going to build, per whatever the app's
260
+ * own `build.rollupOptions.input` says (or the implicit default — Vite
261
+ * treats `<root>/index.html` as the one entry when nothing is configured).
262
+ * Used to decide whether a `document` entry needs injecting at all.
263
+ */
264
+ function absoluteInputPaths(config, root) {
265
+ const input = config.build?.rollupOptions?.input;
266
+ if (input == null) return new Set([path.join(root, "index.html")]);
267
+ if (typeof input === "string") return new Set([path.resolve(root, input)]);
268
+ if (Array.isArray(input)) return new Set(input.map((entry) => path.resolve(root, entry)));
269
+ return new Set(Object.values(input).map((entry) => path.resolve(root, entry)));
270
+ }
271
+ function assertUniqueKeys(manifests) {
272
+ const seen = /* @__PURE__ */ new Set();
273
+ for (const manifest of manifests) {
274
+ if (seen.has(manifest.key)) throw new Error(`[persona-layout-modules] duplicate module key "${manifest.key}" — keys must be unique within an app`);
275
+ seen.add(manifest.key);
276
+ }
277
+ }
278
+ /** Fields shared between the prod and dev manifest — everything but `remote`. */
279
+ function buildManifestBase(definition) {
280
+ const { input } = definition;
281
+ return {
282
+ allowAddingToLayout: input.allowAddingToLayout,
283
+ allowDynamicHeight: input.allowDynamicHeight,
284
+ badge: input.badge,
285
+ category: input.category,
286
+ configSchema: input.configSchema ? z.toJSONSchema(input.configSchema) : void 0,
287
+ defaultSize: input.defaultSize,
288
+ descriptionShort: input.descriptionShort,
289
+ displayName: input.displayName,
290
+ icon: input.icon,
291
+ internalUsersOnly: input.internalUsersOnly,
292
+ key: input.key,
293
+ layouts: input.layouts ?? [],
294
+ manifestVersion: "v1",
295
+ uiConfigSchema: input.uiConfigSchema
296
+ };
297
+ }
298
+ /**
299
+ * Bundles a single `code` entry (with its dependencies inlined) for dev-mode
300
+ * cross-origin serving. Deliberately re-run on every request rather than
301
+ * cached — these are small files and esbuild is fast enough that freshness
302
+ * on every reload matters more than shaving milliseconds.
303
+ */
304
+ async function bundleForDev(absolutePath, root, resolvedConfig) {
305
+ const shimAlias = await ensureRemoteShimFiles();
306
+ const [output] = (await build({
307
+ absWorkingDir: root,
308
+ alias: {
309
+ ...resolveAliasForEsbuild(resolvedConfig),
310
+ ...shimAlias
311
+ },
312
+ bundle: true,
313
+ entryPoints: [absolutePath],
314
+ format: "esm",
315
+ jsx: "automatic",
316
+ sourcemap: "inline",
317
+ write: false
318
+ })).outputFiles;
319
+ if (!output) throw new Error("esbuild produced no output");
320
+ return output.text;
321
+ }
322
+ function checkDeliveryExists(definition, delivery) {
323
+ if (!existsSync(delivery.absolutePath)) throw new Error(`[persona-layout-modules] ${definition.definitionFilePath}: render "${delivery.sourcePath}" does not resolve to a file (${delivery.absolutePath})`);
324
+ }
325
+ function collectCss(chunk, into) {
326
+ for (const fileName of chunk.viteMetadata?.importedCss ?? []) into.add(fileName);
327
+ }
328
+ function devBundleUrl(definition) {
329
+ return `${DEV_BUNDLE_PREFIX}/${entryNameFor(definition)}.js`;
330
+ }
331
+ function devManifestDelivery(root, base, definition, delivery) {
332
+ if (delivery.kind === "document") return { document: documentUrlFor(root, base, delivery) };
333
+ return { code: devBundleUrl(definition) };
334
+ }
335
+ /**
336
+ * A `document` delivery's final PRODUCTION URL: the author's `path` override
337
+ * when given (a logical SPA route, not a file location — used as-is, never
338
+ * joined with `base`, matching how a subapp's own manifest URLs work),
339
+ * otherwise the html file's own path relative to root (which Vite preserves
340
+ * in its build output for multi-page entries) joined with `base`, since
341
+ * that IS a real build-output location.
342
+ */
343
+ function documentUrlFor(root, base, delivery) {
344
+ if (delivery.path) return delivery.path;
345
+ return joinUrl(base, path.relative(root, delivery.absolutePath).split(path.sep).join("/"));
346
+ }
347
+ function entryNameFor(definition) {
348
+ return `layout-module--${definition.input.key}`;
349
+ }
350
+ function findChunkByFacadeModule(emitted, facadeModuleId) {
351
+ for (const file of emitted) if (file.type === "chunk" && file.facadeModuleId === facadeModuleId) return file;
352
+ }
353
+ function joinUrl(base, fileName) {
354
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName}`.replace(/\/{2,}/g, "/");
355
+ }
356
+ function matchDevBundleRequest(url, definitions) {
357
+ if (!url?.startsWith(`${DEV_BUNDLE_PREFIX}/`) || !url.endsWith(".js")) return;
358
+ const name = url.slice(24, -3);
359
+ for (const definition of definitions) if (definition.render.kind === "code" && name === entryNameFor(definition)) return {
360
+ absolutePath: definition.render.absolutePath,
361
+ definition
362
+ };
363
+ }
364
+ /**
365
+ * Vite normalizes `resolve.alias` (however the user wrote it — object or
366
+ * array) into an array of `{ find, replacement }`. esbuild's own `alias`
367
+ * option only accepts exact string keys (no RegExp, no partial-path
368
+ * matching) — a real limitation this doesn't try to hide, just the common
369
+ * case: a plain string-to-string alias map, which is what every `sa-*`
370
+ * subapp's `vite.config.ts` actually uses.
371
+ */
372
+ function resolveAliasForEsbuild(resolvedConfig) {
373
+ const alias = {};
374
+ for (const entry of resolvedConfig.resolve?.alias ?? []) if (typeof entry.find === "string") alias[entry.find] = entry.replacement;
375
+ return alias;
376
+ }
377
+ /** `.html` → `document`; anything else (`.ts`/`.tsx`/`.js`/`.jsx`) → `code`. */
378
+ function resolveEntry(root, spec) {
379
+ const absolutePath = path.resolve(root, spec.entry);
380
+ if (absolutePath.endsWith(".html")) return {
381
+ absolutePath,
382
+ kind: "document",
383
+ path: spec.path,
384
+ sourcePath: spec.entry
385
+ };
386
+ return {
387
+ absolutePath,
388
+ kind: "code",
389
+ sourcePath: spec.entry
390
+ };
391
+ }
392
+ /**
393
+ * Resolves the `render` delivery into its final manifest value for the
394
+ * PRODUCTION path. `document` uses `documentUrlFor` and touches nothing else —
395
+ * no chunk lookup, since it was never part of this plugin's own isolated
396
+ * build. `code` becomes `{ code: <hashed chunk URL> }`, throwing if the
397
+ * isolated build didn't emit a chunk for it. Also returns the chunk itself
398
+ * (when there is one) so the caller can collect its CSS without a second
399
+ * lookup.
400
+ */
401
+ function resolveManifestDelivery(definition, delivery, emitted, root, base) {
402
+ if (delivery.kind === "document") return { value: { document: documentUrlFor(root, base, delivery) } };
403
+ const chunk = findChunkByFacadeModule(emitted, delivery.absolutePath);
404
+ if (!chunk) throw new Error(`[persona-layout-modules] ${definition.definitionFilePath}: could not locate the emitted chunk for render "${delivery.sourcePath}" in the isolated module build.`);
405
+ return {
406
+ chunk,
407
+ value: { code: joinUrl(base, chunk.fileName) }
408
+ };
409
+ }
410
+ /**
411
+ * Normalizes whatever shape `build.rollupOptions.input` currently has (unset,
412
+ * a string, an array, or already an object) into the object form, so a newly
413
+ * injected `document` entry can be merged in as an additional key WITHOUT
414
+ * dropping whatever was already configured — including the implicit default
415
+ * `index.html` entry when nothing was configured at all, which would
416
+ * otherwise silently disappear the moment this plugin assigns an object here.
417
+ */
418
+ function toInputRecord(input, root) {
419
+ if (input == null) return { index: path.join(root, "index.html") };
420
+ if (typeof input === "string") return { index: path.resolve(root, input) };
421
+ if (Array.isArray(input)) {
422
+ const record$1 = {};
423
+ for (const entry of input) {
424
+ const absolutePath = path.resolve(root, entry);
425
+ record$1[path.basename(absolutePath, path.extname(absolutePath))] = absolutePath;
426
+ }
427
+ return record$1;
428
+ }
429
+ const record = {};
430
+ for (const [name, entry] of Object.entries(input)) record[name] = path.resolve(root, entry);
431
+ return record;
432
+ }
433
+
434
+ //#endregion
435
+ export { defineLayoutModule, personaLayoutModules };
package/package.json CHANGED
@@ -1,10 +1,123 @@
1
1
  {
2
2
  "name": "@persona/layout-editor-remote-module",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Placeholder release. Use 0.5.0 or later.",
3
+ "version": "0.5.0",
4
+ "description": "Shared manifest format, Vite plugin, and host/iframe bridge for remotely-defined dashboard layout modules (app-builder Apps and sa-* subapps).",
5
5
  "license": "SEE LICENSE",
6
+ "type": "module",
7
+ "types": "./dist/index.d.mts",
8
+ "typesVersions": {
9
+ "*": {
10
+ "vite-plugin": [
11
+ "dist/vite-plugin.d.mts"
12
+ ],
13
+ "bridge-host": [
14
+ "dist/bridge-host.d.mts"
15
+ ],
16
+ "bridge-frame": [
17
+ "dist/bridge-frame.d.mts"
18
+ ]
19
+ }
20
+ },
21
+ "engines": {
22
+ "node": "^20.19.0 || >=22.12.0"
23
+ },
6
24
  "files": [
25
+ "dist",
26
+ "skills",
7
27
  "LICENSE"
8
28
  ],
9
- "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
10
- }
29
+ "keywords": [
30
+ "tanstack-intent"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsdown",
34
+ "check": "yarn build && yarn skills:check && yarn test:run",
35
+ "skills:check": "intent validate",
36
+ "test": "vitest",
37
+ "test:run": "vitest run",
38
+ "release": "semantic-release"
39
+ },
40
+ "peerDependencies": {
41
+ "esbuild": ">=0.21.0 <1",
42
+ "react": "^17 || ^18 || ^19",
43
+ "react-dom": "^17 || ^18 || ^19",
44
+ "vite": "^5 || ^6 || ^7 || ^8",
45
+ "zod": "^4.3"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "react": {
49
+ "optional": true
50
+ },
51
+ "react-dom": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "devDependencies": {
56
+ "@semantic-release/exec": "^7.1.0",
57
+ "@tanstack/intent": "^0.3.6",
58
+ "@testing-library/dom": "^10.4.1",
59
+ "@testing-library/react": "^16.3.0",
60
+ "@types/node": "^22.10.0",
61
+ "@types/react": "^19.1.12",
62
+ "@types/react-dom": "^19.1.9",
63
+ "@typescript/native": "npm:typescript@^7.0.2",
64
+ "@vitest/browser": "~4.1.11",
65
+ "@vitest/browser-playwright": "~4.1.11",
66
+ "esbuild": "^0.28.0",
67
+ "jsdom": "^30.0.1",
68
+ "playwright": "1.62.1",
69
+ "react": "^18.3.1",
70
+ "react-dom": "^18.3.1",
71
+ "rollup": "^4.0.0",
72
+ "semantic-release": "^25.0.7",
73
+ "semantic-release-monorepo": "^8.0.2",
74
+ "styled-components": "^5.3.5",
75
+ "tsdown": "^0.18.1",
76
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
77
+ "vite": "^7.0.0",
78
+ "vitest": "~4.1.11",
79
+ "zod": "4.3.6"
80
+ },
81
+ "publishConfig": {
82
+ "registry": "https://registry.npmjs.org/",
83
+ "access": "public"
84
+ },
85
+ "release": {
86
+ "extends": "semantic-release-monorepo",
87
+ "branches": [
88
+ "main"
89
+ ],
90
+ "tagFormat": "remote-module-v${version}",
91
+ "plugins": [
92
+ "@semantic-release/commit-analyzer",
93
+ "@semantic-release/release-notes-generator",
94
+ [
95
+ "@semantic-release/exec",
96
+ {
97
+ "prepareCmd": "yarn version ${nextRelease.version}",
98
+ "publishCmd": "yarn npm publish --staged --access public"
99
+ }
100
+ ],
101
+ "@semantic-release/github"
102
+ ]
103
+ },
104
+ "exports": {
105
+ ".": {
106
+ "types": "./dist/index.d.mts",
107
+ "import": "./dist/index.mjs"
108
+ },
109
+ "./vite-plugin": {
110
+ "types": "./dist/vite-plugin.d.mts",
111
+ "import": "./dist/vite-plugin.mjs"
112
+ },
113
+ "./bridge-host": {
114
+ "types": "./dist/bridge-host.d.mts",
115
+ "import": "./dist/bridge-host.mjs"
116
+ },
117
+ "./bridge-frame": {
118
+ "types": "./dist/bridge-frame.d.mts",
119
+ "import": "./dist/bridge-frame.mjs"
120
+ },
121
+ "./package.json": "./package.json"
122
+ }
123
+ }