@aprovan/patchwork 0.1.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.
Files changed (225) hide show
  1. package/.eslintrc.json +22 -0
  2. package/.github/workflows/publish.yml +41 -0
  3. package/.prettierignore +17 -0
  4. package/LICENSE +373 -0
  5. package/README.md +15 -0
  6. package/apps/chat/.utcp_config.json +14 -0
  7. package/apps/chat/.working/widgets/27060b91-a2a5-4272-b243-6eb904bd4070/main.tsx +107 -0
  8. package/apps/chat/index.html +17 -0
  9. package/apps/chat/node_modules/.bin/autoprefixer +17 -0
  10. package/apps/chat/node_modules/.bin/browserslist +17 -0
  11. package/apps/chat/node_modules/.bin/conc +17 -0
  12. package/apps/chat/node_modules/.bin/concurrently +17 -0
  13. package/apps/chat/node_modules/.bin/copilot-proxy +17 -0
  14. package/apps/chat/node_modules/.bin/jiti +17 -0
  15. package/apps/chat/node_modules/.bin/tailwind +17 -0
  16. package/apps/chat/node_modules/.bin/tailwindcss +17 -0
  17. package/apps/chat/node_modules/.bin/tsc +17 -0
  18. package/apps/chat/node_modules/.bin/tsserver +17 -0
  19. package/apps/chat/node_modules/.bin/tsx +17 -0
  20. package/apps/chat/node_modules/.bin/vite +17 -0
  21. package/apps/chat/package.json +55 -0
  22. package/apps/chat/postcss.config.js +6 -0
  23. package/apps/chat/src/App.tsx +7 -0
  24. package/apps/chat/src/components/ui/avatar.tsx +48 -0
  25. package/apps/chat/src/components/ui/badge.tsx +36 -0
  26. package/apps/chat/src/components/ui/button.tsx +56 -0
  27. package/apps/chat/src/components/ui/card.tsx +86 -0
  28. package/apps/chat/src/components/ui/collapsible.tsx +9 -0
  29. package/apps/chat/src/components/ui/dialog.tsx +60 -0
  30. package/apps/chat/src/components/ui/input.tsx +25 -0
  31. package/apps/chat/src/components/ui/scroll-area.tsx +46 -0
  32. package/apps/chat/src/index.css +190 -0
  33. package/apps/chat/src/lib/utils.ts +6 -0
  34. package/apps/chat/src/main.tsx +10 -0
  35. package/apps/chat/src/pages/ChatPage.tsx +460 -0
  36. package/apps/chat/tailwind.config.js +71 -0
  37. package/apps/chat/tsconfig.json +25 -0
  38. package/apps/chat/vite.config.ts +26 -0
  39. package/package.json +35 -0
  40. package/packages/bobbin/node_modules/.bin/esbuild +14 -0
  41. package/packages/bobbin/node_modules/.bin/jiti +17 -0
  42. package/packages/bobbin/node_modules/.bin/tsc +17 -0
  43. package/packages/bobbin/node_modules/.bin/tsserver +17 -0
  44. package/packages/bobbin/node_modules/.bin/tsup +17 -0
  45. package/packages/bobbin/node_modules/.bin/tsup-node +17 -0
  46. package/packages/bobbin/node_modules/.bin/tsx +17 -0
  47. package/packages/bobbin/package.json +30 -0
  48. package/packages/bobbin/src/Bobbin.tsx +89 -0
  49. package/packages/bobbin/src/components/EditPanel/EditPanel.tsx +376 -0
  50. package/packages/bobbin/src/components/EditPanel/controls/ColorPicker.tsx +138 -0
  51. package/packages/bobbin/src/components/EditPanel/controls/QuickSelectDropdown.tsx +142 -0
  52. package/packages/bobbin/src/components/EditPanel/controls/SliderInput.tsx +94 -0
  53. package/packages/bobbin/src/components/EditPanel/controls/SpacingControl.tsx +285 -0
  54. package/packages/bobbin/src/components/EditPanel/controls/ToggleGroup.tsx +37 -0
  55. package/packages/bobbin/src/components/EditPanel/controls/TokenDropdown.tsx +33 -0
  56. package/packages/bobbin/src/components/EditPanel/sections/AnnotationSection.tsx +136 -0
  57. package/packages/bobbin/src/components/EditPanel/sections/BackgroundSection.tsx +79 -0
  58. package/packages/bobbin/src/components/EditPanel/sections/EffectsSection.tsx +85 -0
  59. package/packages/bobbin/src/components/EditPanel/sections/LayoutSection.tsx +224 -0
  60. package/packages/bobbin/src/components/EditPanel/sections/SectionWrapper.tsx +57 -0
  61. package/packages/bobbin/src/components/EditPanel/sections/SizeSection.tsx +166 -0
  62. package/packages/bobbin/src/components/EditPanel/sections/SpacingSection.tsx +69 -0
  63. package/packages/bobbin/src/components/EditPanel/sections/TypographySection.tsx +148 -0
  64. package/packages/bobbin/src/components/Inspector/Inspector.tsx +221 -0
  65. package/packages/bobbin/src/components/Overlay/ControlHandles.tsx +572 -0
  66. package/packages/bobbin/src/components/Overlay/MarginPaddingOverlay.tsx +229 -0
  67. package/packages/bobbin/src/components/Overlay/SelectionOverlay.tsx +73 -0
  68. package/packages/bobbin/src/components/Pill/Pill.tsx +155 -0
  69. package/packages/bobbin/src/components/ThemeToggle/ThemeToggle.tsx +72 -0
  70. package/packages/bobbin/src/core/changeSerializer.ts +139 -0
  71. package/packages/bobbin/src/core/useBobbin.ts +399 -0
  72. package/packages/bobbin/src/core/useChangeTracker.ts +186 -0
  73. package/packages/bobbin/src/core/useClipboard.ts +21 -0
  74. package/packages/bobbin/src/core/useElementSelection.ts +146 -0
  75. package/packages/bobbin/src/index.ts +46 -0
  76. package/packages/bobbin/src/tokens/borders.ts +19 -0
  77. package/packages/bobbin/src/tokens/colors.ts +150 -0
  78. package/packages/bobbin/src/tokens/index.ts +37 -0
  79. package/packages/bobbin/src/tokens/shadows.ts +10 -0
  80. package/packages/bobbin/src/tokens/spacing.ts +37 -0
  81. package/packages/bobbin/src/tokens/typography.ts +51 -0
  82. package/packages/bobbin/src/types.ts +157 -0
  83. package/packages/bobbin/src/utils/animation.ts +40 -0
  84. package/packages/bobbin/src/utils/dom.ts +36 -0
  85. package/packages/bobbin/src/utils/selectors.ts +76 -0
  86. package/packages/bobbin/tsconfig.json +10 -0
  87. package/packages/bobbin/tsup.config.ts +10 -0
  88. package/packages/compiler/node_modules/.bin/esbuild +17 -0
  89. package/packages/compiler/node_modules/.bin/jiti +17 -0
  90. package/packages/compiler/node_modules/.bin/tsc +17 -0
  91. package/packages/compiler/node_modules/.bin/tsserver +17 -0
  92. package/packages/compiler/node_modules/.bin/tsup +17 -0
  93. package/packages/compiler/node_modules/.bin/tsup-node +17 -0
  94. package/packages/compiler/node_modules/.bin/tsx +17 -0
  95. package/packages/compiler/package.json +38 -0
  96. package/packages/compiler/src/compiler.ts +258 -0
  97. package/packages/compiler/src/images/index.ts +13 -0
  98. package/packages/compiler/src/images/loader.ts +234 -0
  99. package/packages/compiler/src/images/registry.ts +112 -0
  100. package/packages/compiler/src/index.ts +141 -0
  101. package/packages/compiler/src/mount/bridge.ts +399 -0
  102. package/packages/compiler/src/mount/embedded.ts +306 -0
  103. package/packages/compiler/src/mount/iframe.ts +433 -0
  104. package/packages/compiler/src/mount/index.ts +18 -0
  105. package/packages/compiler/src/schemas.ts +169 -0
  106. package/packages/compiler/src/transforms/cdn.ts +411 -0
  107. package/packages/compiler/src/transforms/index.ts +4 -0
  108. package/packages/compiler/src/transforms/vfs.ts +138 -0
  109. package/packages/compiler/src/types.ts +233 -0
  110. package/packages/compiler/src/vfs/backends/indexeddb.ts +66 -0
  111. package/packages/compiler/src/vfs/backends/local-fs.ts +41 -0
  112. package/packages/compiler/src/vfs/backends/s3.ts +60 -0
  113. package/packages/compiler/src/vfs/index.ts +11 -0
  114. package/packages/compiler/src/vfs/project.ts +56 -0
  115. package/packages/compiler/src/vfs/store.ts +53 -0
  116. package/packages/compiler/src/vfs/types.ts +20 -0
  117. package/packages/compiler/tsconfig.json +8 -0
  118. package/packages/compiler/tsup.config.ts +14 -0
  119. package/packages/editor/node_modules/.bin/jiti +17 -0
  120. package/packages/editor/node_modules/.bin/tsc +17 -0
  121. package/packages/editor/node_modules/.bin/tsserver +17 -0
  122. package/packages/editor/node_modules/.bin/tsup +17 -0
  123. package/packages/editor/node_modules/.bin/tsup-node +17 -0
  124. package/packages/editor/node_modules/.bin/tsx +17 -0
  125. package/packages/editor/package.json +45 -0
  126. package/packages/editor/src/components/CodeBlockExtension.tsx +190 -0
  127. package/packages/editor/src/components/CodePreview.tsx +344 -0
  128. package/packages/editor/src/components/MarkdownEditor.tsx +270 -0
  129. package/packages/editor/src/components/ServicesInspector.tsx +118 -0
  130. package/packages/editor/src/components/edit/EditHistory.tsx +89 -0
  131. package/packages/editor/src/components/edit/EditModal.tsx +236 -0
  132. package/packages/editor/src/components/edit/FileTree.tsx +144 -0
  133. package/packages/editor/src/components/edit/api.ts +100 -0
  134. package/packages/editor/src/components/edit/index.ts +6 -0
  135. package/packages/editor/src/components/edit/types.ts +53 -0
  136. package/packages/editor/src/components/edit/useEditSession.ts +164 -0
  137. package/packages/editor/src/components/index.ts +5 -0
  138. package/packages/editor/src/index.ts +72 -0
  139. package/packages/editor/src/lib/code-extractor.ts +210 -0
  140. package/packages/editor/src/lib/diff.ts +308 -0
  141. package/packages/editor/src/lib/index.ts +4 -0
  142. package/packages/editor/src/lib/utils.ts +6 -0
  143. package/packages/editor/src/lib/vfs.ts +106 -0
  144. package/packages/editor/tsconfig.json +10 -0
  145. package/packages/editor/tsup.config.ts +10 -0
  146. package/packages/images/ink/node_modules/.bin/jiti +17 -0
  147. package/packages/images/ink/node_modules/.bin/tsc +17 -0
  148. package/packages/images/ink/node_modules/.bin/tsserver +17 -0
  149. package/packages/images/ink/node_modules/.bin/tsup +17 -0
  150. package/packages/images/ink/node_modules/.bin/tsup-node +17 -0
  151. package/packages/images/ink/node_modules/.bin/tsx +17 -0
  152. package/packages/images/ink/package.json +53 -0
  153. package/packages/images/ink/src/index.ts +48 -0
  154. package/packages/images/ink/src/runner.ts +331 -0
  155. package/packages/images/ink/src/setup.ts +123 -0
  156. package/packages/images/ink/tsconfig.json +10 -0
  157. package/packages/images/ink/tsup.config.ts +11 -0
  158. package/packages/images/shadcn/node_modules/.bin/jiti +17 -0
  159. package/packages/images/shadcn/node_modules/.bin/tsc +17 -0
  160. package/packages/images/shadcn/node_modules/.bin/tsserver +17 -0
  161. package/packages/images/shadcn/node_modules/.bin/tsup +17 -0
  162. package/packages/images/shadcn/node_modules/.bin/tsup-node +17 -0
  163. package/packages/images/shadcn/node_modules/.bin/tsx +17 -0
  164. package/packages/images/shadcn/package.json +82 -0
  165. package/packages/images/shadcn/src/html.ts +341 -0
  166. package/packages/images/shadcn/src/index.ts +37 -0
  167. package/packages/images/shadcn/src/setup.ts +287 -0
  168. package/packages/images/shadcn/tsconfig.json +9 -0
  169. package/packages/images/shadcn/tsup.config.ts +13 -0
  170. package/packages/images/vanilla/node_modules/.bin/jiti +17 -0
  171. package/packages/images/vanilla/node_modules/.bin/tsc +17 -0
  172. package/packages/images/vanilla/node_modules/.bin/tsserver +17 -0
  173. package/packages/images/vanilla/node_modules/.bin/tsup +17 -0
  174. package/packages/images/vanilla/node_modules/.bin/tsup-node +17 -0
  175. package/packages/images/vanilla/node_modules/.bin/tsx +17 -0
  176. package/packages/images/vanilla/package.json +35 -0
  177. package/packages/images/vanilla/src/index.ts +7 -0
  178. package/packages/images/vanilla/src/setup.ts +6 -0
  179. package/packages/images/vanilla/tsconfig.json +9 -0
  180. package/packages/images/vanilla/tsup.config.ts +10 -0
  181. package/packages/patchwork/node_modules/.bin/jiti +17 -0
  182. package/packages/patchwork/node_modules/.bin/tsc +17 -0
  183. package/packages/patchwork/node_modules/.bin/tsserver +17 -0
  184. package/packages/patchwork/node_modules/.bin/tsup +17 -0
  185. package/packages/patchwork/node_modules/.bin/tsup-node +17 -0
  186. package/packages/patchwork/node_modules/.bin/tsx +17 -0
  187. package/packages/patchwork/package.json +27 -0
  188. package/packages/patchwork/src/index.ts +15 -0
  189. package/packages/patchwork/src/services/index.ts +11 -0
  190. package/packages/patchwork/src/services/proxy.ts +213 -0
  191. package/packages/patchwork/src/services/types.ts +28 -0
  192. package/packages/patchwork/src/types.ts +116 -0
  193. package/packages/patchwork/tsconfig.json +8 -0
  194. package/packages/patchwork/tsup.config.ts +14 -0
  195. package/packages/stitchery/node_modules/.bin/jiti +17 -0
  196. package/packages/stitchery/node_modules/.bin/tsc +17 -0
  197. package/packages/stitchery/node_modules/.bin/tsserver +17 -0
  198. package/packages/stitchery/node_modules/.bin/tsup +17 -0
  199. package/packages/stitchery/node_modules/.bin/tsup-node +17 -0
  200. package/packages/stitchery/node_modules/.bin/tsx +17 -0
  201. package/packages/stitchery/package.json +40 -0
  202. package/packages/stitchery/src/cli.ts +116 -0
  203. package/packages/stitchery/src/index.ts +16 -0
  204. package/packages/stitchery/src/prompts.ts +326 -0
  205. package/packages/stitchery/src/server/index.ts +365 -0
  206. package/packages/stitchery/src/server/local-packages.ts +91 -0
  207. package/packages/stitchery/src/server/routes.ts +122 -0
  208. package/packages/stitchery/src/server/services.ts +382 -0
  209. package/packages/stitchery/src/server/vfs-routes.ts +142 -0
  210. package/packages/stitchery/src/types.ts +59 -0
  211. package/packages/stitchery/tsconfig.json +13 -0
  212. package/packages/stitchery/tsup.config.ts +15 -0
  213. package/packages/utcp/node_modules/.bin/jiti +17 -0
  214. package/packages/utcp/node_modules/.bin/tsc +17 -0
  215. package/packages/utcp/node_modules/.bin/tsserver +17 -0
  216. package/packages/utcp/node_modules/.bin/tsup +17 -0
  217. package/packages/utcp/node_modules/.bin/tsup-node +17 -0
  218. package/packages/utcp/node_modules/.bin/tsx +17 -0
  219. package/packages/utcp/package.json +38 -0
  220. package/packages/utcp/src/index.ts +153 -0
  221. package/packages/utcp/tsconfig.json +8 -0
  222. package/packages/utcp/tsup.config.ts +12 -0
  223. package/pnpm-workspace.yaml +3 -0
  224. package/tsconfig.json +18 -0
  225. package/turbo.json +23 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Iframe mount mode - mounts widgets in sandboxed iframes
3
+ *
4
+ * For untrusted widgets that need isolation.
5
+ */
6
+
7
+ import type {
8
+ CompiledWidget,
9
+ LoadedImage,
10
+ MountedWidget,
11
+ MountOptions,
12
+ ServiceProxy,
13
+ } from '../types.js';
14
+ import { ParentBridge, generateIframeBridgeScript } from './bridge.js';
15
+ import { generateImportMap } from '../transforms/cdn.js';
16
+
17
+ let mountCounter = 0;
18
+
19
+ // Shared bridge for all iframes
20
+ let sharedBridge: ParentBridge | null = null;
21
+
22
+ /**
23
+ * Get or create the shared parent bridge
24
+ */
25
+ function getParentBridge(proxy: ServiceProxy): ParentBridge {
26
+ if (!sharedBridge) {
27
+ sharedBridge = new ParentBridge(proxy);
28
+ }
29
+ return sharedBridge;
30
+ }
31
+
32
+ /**
33
+ * Generate a unique mount ID
34
+ */
35
+ function generateMountId(): string {
36
+ return `pw-iframe-${Date.now()}-${++mountCounter}`;
37
+ }
38
+
39
+ /**
40
+ * Default sandbox attributes for iframes (production)
41
+ *
42
+ * By default, iframes are strictly sandboxed without same-origin access.
43
+ * This is the safest option when widgets load all dependencies from external CDNs.
44
+ */
45
+ const DEFAULT_SANDBOX = ['allow-scripts'];
46
+
47
+ /**
48
+ * Development sandbox attributes - includes allow-same-origin
49
+ *
50
+ * allow-same-origin is required when:
51
+ * - Fetching modules from the parent origin (e.g., /_local-packages/ in dev)
52
+ * - Using import maps that reference parent-relative URLs
53
+ * - Accessing the parent's CDN proxy
54
+ *
55
+ * Note: This does NOT allow the iframe to access parent's DOM or cookies,
56
+ * but it does allow same-origin network requests.
57
+ *
58
+ * WARNING: Combining allow-scripts + allow-same-origin allows the iframe to
59
+ * escape its sandbox. Only use in development or when hosting on a separate subdomain.
60
+ */
61
+ export const DEV_SANDBOX = ['allow-scripts', 'allow-same-origin'];
62
+
63
+ /**
64
+ * Generate the HTML content for the iframe
65
+ */
66
+ function generateIframeContent(
67
+ image: LoadedImage | null,
68
+ inputs: Record<string, unknown>,
69
+ services: string[],
70
+ baseUrl: string,
71
+ ): string {
72
+ const bridgeScript = generateIframeBridgeScript(services);
73
+
74
+ // Generate import map from image dependencies and manifest packages
75
+ const packages = {
76
+ ...(image?.dependencies || {}),
77
+ };
78
+ const importMap = generateImportMap(packages);
79
+
80
+ // CSS from image
81
+ const css = image?.css || '';
82
+
83
+ const frameworkConfig = image?.config?.framework || {};
84
+ const preloadUrls = frameworkConfig.preload || [];
85
+ const globals = frameworkConfig.globals || {};
86
+ const globalNames = Object.values(globals);
87
+ const imageModuleUrl = image?.moduleUrl || '';
88
+
89
+ const mountScript = `
90
+ // Run image setup inside the iframe (styling/runtime)
91
+ const imageModuleUrl = ${JSON.stringify(imageModuleUrl)};
92
+
93
+ // Preload framework modules declared by the image (if any)
94
+ const preloadUrls = ${JSON.stringify(preloadUrls)};
95
+ const globalNames = ${JSON.stringify(globalNames)};
96
+ for (let i = 0; i < preloadUrls.length; i++) {
97
+ const url = preloadUrls[i];
98
+ const name = globalNames[i];
99
+ if (!url || !name) continue;
100
+ try {
101
+ const mod = await import(/* webpackIgnore: true */ url);
102
+ window[name] = mod;
103
+ } catch (e) {
104
+ console.error('[patchwork-iframe] Failed to preload:', url, e);
105
+ }
106
+ }
107
+
108
+ const root = document.getElementById('root');
109
+ const inputs = window.__PATCHWORK_INPUTS__ || {};
110
+
111
+ if (imageModuleUrl && root) {
112
+ try {
113
+ const img = await import(/* webpackIgnore: true */ imageModuleUrl);
114
+ if (typeof img?.setup === 'function') {
115
+ await img.setup(root);
116
+ }
117
+ } catch (e) {
118
+ console.error('[patchwork-iframe] Failed to run image setup:', e);
119
+ }
120
+ }
121
+
122
+ function pickCreateElement(globals) {
123
+ for (const obj of globals) {
124
+ if (obj && typeof obj.createElement === 'function') return obj.createElement.bind(obj);
125
+ if (obj?.default && typeof obj.default.createElement === 'function') return obj.default.createElement.bind(obj.default);
126
+ }
127
+ return null;
128
+ }
129
+
130
+ function pickRenderer(globals) {
131
+ for (const obj of globals) {
132
+ if (obj && typeof obj.createRoot === 'function') {
133
+ return {
134
+ kind: 'root',
135
+ createRoot: obj.createRoot.bind(obj),
136
+ };
137
+ }
138
+ if (obj && typeof obj.render === 'function') {
139
+ return {
140
+ kind: 'render',
141
+ render: obj.render.bind(obj),
142
+ };
143
+ }
144
+ if (obj?.default && typeof obj.default.createRoot === 'function') {
145
+ return {
146
+ kind: 'root',
147
+ createRoot: obj.default.createRoot.bind(obj.default),
148
+ };
149
+ }
150
+ if (obj?.default && typeof obj.default.render === 'function') {
151
+ return {
152
+ kind: 'render',
153
+ render: obj.default.render.bind(obj.default),
154
+ };
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+
160
+ function getGlobalsFromConfig() {
161
+ const names = ${JSON.stringify(globalNames)};
162
+ return names.map((n) => window[n]).filter(Boolean);
163
+ }
164
+
165
+ async function mountModule(mod) {
166
+ if (!root) throw new Error('No #root element');
167
+
168
+ if (typeof mod?.mount === 'function') {
169
+ const cleanup = await mod.mount(root, inputs);
170
+ if (typeof cleanup === 'function') window.__PATCHWORK_CLEANUP__ = cleanup;
171
+ return;
172
+ }
173
+
174
+ if (typeof mod?.render === 'function') {
175
+ const cleanup = await mod.render(root, inputs);
176
+ if (typeof cleanup === 'function') window.__PATCHWORK_CLEANUP__ = cleanup;
177
+ return;
178
+ }
179
+
180
+ const Component = mod?.default;
181
+ if (typeof Component !== 'function') {
182
+ root.textContent = 'Widget did not export a default component.';
183
+ return;
184
+ }
185
+
186
+ const globals = getGlobalsFromConfig();
187
+ const createElement = pickCreateElement(globals);
188
+ const renderer = pickRenderer(globals);
189
+
190
+ if (createElement && renderer?.kind === 'root') {
191
+ const r = renderer.createRoot(root);
192
+ r.render(createElement(Component, inputs));
193
+ if (typeof r.unmount === 'function') window.__PATCHWORK_CLEANUP__ = () => r.unmount();
194
+ return;
195
+ }
196
+
197
+ if (createElement && renderer?.kind === 'render') {
198
+ renderer.render(createElement(Component, inputs), root);
199
+ return;
200
+ }
201
+
202
+ const result = Component(inputs);
203
+ if (result instanceof HTMLElement) {
204
+ root.appendChild(result);
205
+ return;
206
+ }
207
+ if (typeof result === 'string') {
208
+ root.innerHTML = result;
209
+ return;
210
+ }
211
+
212
+ root.textContent = 'No framework renderer available for this widget.';
213
+ }
214
+
215
+ // Wait for widget code via postMessage (more efficient than inline in srcdoc)
216
+ // We convert relative URLs to absolute so they work inside blob URL context
217
+ window.addEventListener('message', async function handleWidgetCode(event) {
218
+ if (!event.data || event.data.type !== 'widget-code') return;
219
+ window.removeEventListener('message', handleWidgetCode);
220
+
221
+ const widgetCode = event.data.code;
222
+ const origin = event.data.origin || ''; // Parent sends the origin
223
+
224
+ // Convert relative URLs (starting with /) to absolute URLs
225
+ // This is necessary because blob: URLs can't resolve relative imports
226
+ // and srcdoc iframes have null origin
227
+ const absoluteCode = widgetCode.replace(
228
+ /from\\s*["'](\\/[^"']+)["']/g,
229
+ (_, path) => 'from "' + origin + path + '"'
230
+ ).replace(
231
+ /import\\s*["'](\\/[^"']+)["']/g,
232
+ (_, path) => 'import "' + origin + path + '"'
233
+ );
234
+
235
+ const blob = new Blob([absoluteCode], { type: 'application/javascript' });
236
+ const url = URL.createObjectURL(blob);
237
+
238
+ try {
239
+ const mod = await import(/* webpackIgnore: true */ url);
240
+ await mountModule(mod);
241
+ window.parent.postMessage({ type: 'widget-mounted' }, '*');
242
+ } catch (e) {
243
+ console.error('[patchwork-iframe] Failed to mount widget:', e);
244
+ window.parent.postMessage({ type: 'widget-error', error: e.message }, '*');
245
+ } finally {
246
+ URL.revokeObjectURL(url);
247
+ }
248
+ });
249
+
250
+ // Signal ready to receive widget code
251
+ window.parent.postMessage({ type: 'widget-ready' }, '*');
252
+
253
+ // Set up ResizeObserver to report body size changes to parent
254
+ const resizeObserver = new ResizeObserver((entries) => {
255
+ for (const entry of entries) {
256
+ const { width, height } = entry.contentRect;
257
+ window.parent.postMessage({
258
+ type: 'widget-resize',
259
+ width: Math.ceil(width),
260
+ height: Math.ceil(height)
261
+ }, '*');
262
+ }
263
+ });
264
+ resizeObserver.observe(document.body);
265
+ `;
266
+
267
+ return `<!DOCTYPE html>
268
+ <html>
269
+ <head>
270
+ <base href="${baseUrl}">
271
+ <meta charset="UTF-8">
272
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
273
+ <style>
274
+ * { box-sizing: border-box; margin: 0; padding: 0; }
275
+ body { font-family: system-ui, -apple-system, sans-serif; }
276
+ ${css}
277
+ </style>
278
+ <script type="importmap">
279
+ ${JSON.stringify({ imports: importMap }, null, 2)}
280
+ </script>
281
+ </head>
282
+ <body>
283
+ <div id="root"></div>
284
+
285
+ <!-- Service Bridge -->
286
+ <script>
287
+ ${bridgeScript}
288
+ </script>
289
+
290
+ <!-- Widget Inputs -->
291
+ <script>
292
+ window.__PATCHWORK_INPUTS__ = ${JSON.stringify(inputs)};
293
+ </script>
294
+
295
+ <script type="module">
296
+ ${mountScript}
297
+ </script>
298
+ </body>
299
+ </html>`;
300
+ }
301
+
302
+ /**
303
+ * Mount a widget in iframe mode (sandboxed)
304
+ */
305
+ export async function mountIframe(
306
+ widget: CompiledWidget,
307
+ options: MountOptions,
308
+ image: LoadedImage | null,
309
+ proxy: ServiceProxy,
310
+ ): Promise<MountedWidget> {
311
+ const { target, sandbox = DEFAULT_SANDBOX, inputs = {} } = options;
312
+ const mountId = generateMountId();
313
+
314
+ // Create iframe
315
+ const iframe = document.createElement('iframe');
316
+ iframe.id = mountId;
317
+ iframe.className = 'patchwork-widget patchwork-iframe';
318
+ iframe.style.cssText = 'width: 100%; border: none; overflow: hidden;';
319
+ iframe.sandbox.add(...sandbox);
320
+
321
+ // Register with bridge before loading content
322
+ const bridge = getParentBridge(proxy);
323
+ bridge.registerIframe(iframe);
324
+
325
+ // Generate and set iframe content (without widget code)
326
+ // Use window.location.origin as base URL so relative paths like /_local-packages/ resolve correctly
327
+ const services = widget.manifest.services || [];
328
+ const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
329
+ const content = generateIframeContent(image, inputs, services, baseUrl);
330
+ iframe.srcdoc = content;
331
+
332
+ // Append to target
333
+ target.appendChild(iframe);
334
+
335
+ // Handle resize messages from iframe
336
+ const handleResize = (event: MessageEvent) => {
337
+ if (event.source !== iframe.contentWindow) return;
338
+ if (event.data?.type === 'widget-resize') {
339
+ const { height } = event.data;
340
+ if (typeof height === 'number' && height > 0) {
341
+ iframe.style.height = `${height}px`;
342
+ }
343
+ }
344
+ };
345
+ window.addEventListener('message', handleResize);
346
+
347
+ // Wait for iframe to signal ready, then send widget code
348
+ await new Promise<void>((resolve, reject) => {
349
+ const timeout = setTimeout(() => {
350
+ cleanup();
351
+ reject(new Error('Iframe mount timeout'));
352
+ }, 30000);
353
+
354
+ const handleMessage = (event: MessageEvent) => {
355
+ if (event.source !== iframe.contentWindow) return;
356
+
357
+ if (event.data?.type === 'widget-ready') {
358
+ // Send widget code and origin for URL rewriting
359
+ iframe.contentWindow?.postMessage(
360
+ { type: 'widget-code', code: widget.code, origin: baseUrl },
361
+ '*',
362
+ );
363
+ } else if (event.data?.type === 'widget-mounted') {
364
+ cleanup();
365
+ resolve();
366
+ } else if (event.data?.type === 'widget-error') {
367
+ cleanup();
368
+ reject(new Error(event.data.error || 'Widget mount failed'));
369
+ }
370
+ };
371
+
372
+ const cleanup = () => {
373
+ clearTimeout(timeout);
374
+ window.removeEventListener('message', handleMessage);
375
+ };
376
+
377
+ window.addEventListener('message', handleMessage);
378
+ });
379
+
380
+ // Create unmount function
381
+ const unmount = () => {
382
+ window.removeEventListener('message', handleResize);
383
+ bridge.unregisterIframe(iframe);
384
+ iframe.remove();
385
+ };
386
+
387
+ return {
388
+ id: mountId,
389
+ widget,
390
+ mode: 'iframe',
391
+ target,
392
+ iframe,
393
+ inputs,
394
+ sandbox,
395
+ unmount,
396
+ };
397
+ }
398
+
399
+ /**
400
+ * Hot reload an iframe widget
401
+ */
402
+ export async function reloadIframe(
403
+ mounted: MountedWidget,
404
+ widget: CompiledWidget,
405
+ image: LoadedImage | null,
406
+ proxy: ServiceProxy,
407
+ ): Promise<MountedWidget> {
408
+ // Unmount existing
409
+ mounted.unmount();
410
+
411
+ // Remount with new widget
412
+ return mountIframe(
413
+ widget,
414
+ {
415
+ target: mounted.target,
416
+ mode: 'iframe',
417
+ sandbox: mounted.sandbox,
418
+ inputs: mounted.inputs,
419
+ },
420
+ image,
421
+ proxy,
422
+ );
423
+ }
424
+
425
+ /**
426
+ * Dispose the shared bridge (call on app shutdown)
427
+ */
428
+ export function disposeIframeBridge(): void {
429
+ if (sharedBridge) {
430
+ sharedBridge.dispose();
431
+ sharedBridge = null;
432
+ }
433
+ }
@@ -0,0 +1,18 @@
1
+ export { mountEmbedded, reloadEmbedded } from './embedded.js';
2
+ export {
3
+ mountIframe,
4
+ reloadIframe,
5
+ disposeIframeBridge,
6
+ DEV_SANDBOX,
7
+ } from './iframe.js';
8
+ export {
9
+ createHttpServiceProxy,
10
+ createFieldAccessProxy,
11
+ generateNamespaceGlobals,
12
+ injectNamespaceGlobals,
13
+ removeNamespaceGlobals,
14
+ extractNamespaces,
15
+ ParentBridge,
16
+ createIframeServiceProxy,
17
+ generateIframeBridgeScript,
18
+ } from './bridge.js';
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Zod schemas for Patchwork compiler types
3
+ *
4
+ * These schemas validate:
5
+ * - ImageConfig from package.json patchwork field
6
+ * - Widget manifests
7
+ * - Input specifications
8
+ */
9
+
10
+ import { z } from 'zod';
11
+
12
+ // Platform schema
13
+ export const PlatformSchema = z.enum(['browser', 'cli']);
14
+ export type Platform = z.infer<typeof PlatformSchema>;
15
+
16
+ // esbuild configuration schema
17
+ export const EsbuildConfigSchema = z
18
+ .object({
19
+ target: z.string().optional(),
20
+ format: z.enum(['esm', 'cjs', 'iife']).optional(),
21
+ jsx: z.enum(['automatic', 'transform', 'preserve']).optional(),
22
+ jsxFactory: z.string().optional(),
23
+ jsxFragment: z.string().optional(),
24
+ })
25
+ .strict()
26
+ .optional();
27
+
28
+ export type EsbuildConfig = z.infer<typeof EsbuildConfigSchema>;
29
+
30
+ // Framework configuration - specifies globals and CDN URLs for framework deps
31
+ export const FrameworkConfigSchema = z
32
+ .object({
33
+ // Map of package names to window global names (e.g., { react: 'React' })
34
+ globals: z.record(z.string(), z.string()).optional(),
35
+ // CDN URLs to preload before widget execution
36
+ preload: z.array(z.string()).optional(),
37
+ // Dependency version overrides for CDN packages (e.g., { react: '18' })
38
+ deps: z.record(z.string(), z.string()).optional(),
39
+ })
40
+ .strict()
41
+ .optional();
42
+
43
+ export type FrameworkConfig = z.infer<typeof FrameworkConfigSchema>;
44
+
45
+ // Aliases schema - maps path patterns to target packages
46
+ export const AliasesSchema = z.record(z.string(), z.string()).optional();
47
+
48
+ export type Aliases = z.infer<typeof AliasesSchema>;
49
+
50
+ // Dependencies schema - maps package names to version specifiers
51
+ export const DependenciesSchema = z.record(z.string(), z.string()).optional();
52
+
53
+ // ImageConfig schema - validates package.json patchwork field
54
+ export const ImageConfigSchema = z
55
+ .object({
56
+ platform: PlatformSchema,
57
+ dependencies: DependenciesSchema,
58
+ esbuild: EsbuildConfigSchema,
59
+ framework: FrameworkConfigSchema,
60
+ aliases: AliasesSchema,
61
+ })
62
+ .strict();
63
+
64
+ export type ImageConfig = z.infer<typeof ImageConfigSchema>;
65
+
66
+ // Input specification schema
67
+ export const InputSpecSchema = z.object({
68
+ type: z.enum(['string', 'number', 'boolean', 'object', 'array']),
69
+ default: z.unknown().optional(),
70
+ required: z.boolean().optional(),
71
+ description: z.string().optional(),
72
+ });
73
+
74
+ export type InputSpec = z.infer<typeof InputSpecSchema>;
75
+
76
+ // Widget manifest schema
77
+ export const ManifestSchema = z.object({
78
+ name: z.string(),
79
+ version: z.string(),
80
+ description: z.string().optional(),
81
+ platform: PlatformSchema,
82
+ image: z.string(),
83
+ inputs: z.record(z.string(), InputSpecSchema).optional(),
84
+ services: z.array(z.string()).optional(),
85
+ packages: z.record(z.string(), z.string()).optional(),
86
+ });
87
+
88
+ export type Manifest = z.infer<typeof ManifestSchema>;
89
+
90
+ // Compile options schema
91
+ export const CompileOptionsSchema = z
92
+ .object({
93
+ typescript: z.boolean().optional(),
94
+ })
95
+ .strict()
96
+ .optional();
97
+
98
+ export type CompileOptions = z.infer<typeof CompileOptionsSchema>;
99
+
100
+ // Mount mode schema
101
+ export const MountModeSchema = z.enum(['embedded', 'iframe']);
102
+ export type MountMode = z.infer<typeof MountModeSchema>;
103
+
104
+ // Mount options schema
105
+ export const MountOptionsSchema = z.object({
106
+ target: z.custom<HTMLElement>((v) => v instanceof HTMLElement, {
107
+ message: 'Expected HTMLElement',
108
+ }),
109
+ mode: MountModeSchema,
110
+ sandbox: z.array(z.string()).optional(),
111
+ inputs: z.record(z.string(), z.unknown()).optional(),
112
+ });
113
+
114
+ export type MountOptions = z.infer<typeof MountOptionsSchema>;
115
+
116
+ /**
117
+ * Parse and validate ImageConfig from package.json patchwork field
118
+ *
119
+ * @param data - Raw data from package.json patchwork field
120
+ * @returns Validated ImageConfig
121
+ * @throws z.ZodError if validation fails
122
+ */
123
+ export function parseImageConfig(data: unknown): ImageConfig {
124
+ return ImageConfigSchema.parse(data);
125
+ }
126
+
127
+ /**
128
+ * Safely parse ImageConfig, returning null on failure
129
+ */
130
+ export function safeParseImageConfig(data: unknown): ImageConfig | null {
131
+ const result = ImageConfigSchema.safeParse(data);
132
+ return result.success ? result.data : null;
133
+ }
134
+
135
+ /**
136
+ * Parse and validate widget manifest
137
+ */
138
+ export function parseManifest(data: unknown): Manifest {
139
+ return ManifestSchema.parse(data);
140
+ }
141
+
142
+ /**
143
+ * Safely parse manifest, returning null on failure
144
+ */
145
+ export function safeParseManifest(data: unknown): Manifest | null {
146
+ const result = ManifestSchema.safeParse(data);
147
+ return result.success ? result.data : null;
148
+ }
149
+
150
+ // Default ImageConfig for fallback
151
+ export const DEFAULT_IMAGE_CONFIG: ImageConfig = {
152
+ platform: 'browser',
153
+ esbuild: {
154
+ target: 'es2020',
155
+ format: 'esm',
156
+ jsx: 'automatic',
157
+ },
158
+ framework: {},
159
+ };
160
+
161
+ // Default CLI ImageConfig for fallback
162
+ export const DEFAULT_CLI_IMAGE_CONFIG: ImageConfig = {
163
+ platform: 'cli',
164
+ esbuild: {
165
+ target: 'node20',
166
+ format: 'esm',
167
+ jsx: 'automatic',
168
+ },
169
+ };