@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,411 @@
1
+ /**
2
+ * CDN transform - converts bare imports to CDN URLs (esm.sh)
3
+ */
4
+
5
+ import type { Plugin } from 'esbuild-wasm';
6
+
7
+ const DEFAULT_CDN_BASE = 'https://esm.sh';
8
+ let cdnBaseUrl = DEFAULT_CDN_BASE;
9
+
10
+ export function setCdnBaseUrl(url: string): void {
11
+ cdnBaseUrl = url;
12
+ }
13
+
14
+ export function getCdnBaseUrl(): string {
15
+ return cdnBaseUrl;
16
+ }
17
+
18
+ // Packages that should be externalized (not bundled from CDN)
19
+ const EXTERNAL_PACKAGES = new Set(['react', 'react-dom', 'ink']);
20
+
21
+ // Built-in Node.js modules that should remain external
22
+ const NODE_BUILTINS = new Set([
23
+ 'assert',
24
+ 'buffer',
25
+ 'child_process',
26
+ 'cluster',
27
+ 'crypto',
28
+ 'dgram',
29
+ 'dns',
30
+ 'events',
31
+ 'fs',
32
+ 'http',
33
+ 'http2',
34
+ 'https',
35
+ 'net',
36
+ 'os',
37
+ 'path',
38
+ 'perf_hooks',
39
+ 'process',
40
+ 'querystring',
41
+ 'readline',
42
+ 'stream',
43
+ 'string_decoder',
44
+ 'timers',
45
+ 'tls',
46
+ 'tty',
47
+ 'url',
48
+ 'util',
49
+ 'v8',
50
+ 'vm',
51
+ 'worker_threads',
52
+ 'zlib',
53
+ ]);
54
+
55
+ /**
56
+ * Parse a package specifier into name and version
57
+ */
58
+ export function parsePackageSpec(spec: string): {
59
+ name: string;
60
+ version?: string;
61
+ } {
62
+ // Handle scoped packages (@scope/name)
63
+ if (spec.startsWith('@')) {
64
+ const parts = spec.split('/');
65
+ if (parts.length >= 2) {
66
+ const scope = parts[0];
67
+ const nameAndVersion = parts.slice(1).join('/');
68
+ const atIndex = nameAndVersion.lastIndexOf('@');
69
+ if (atIndex > 0) {
70
+ return {
71
+ name: `${scope}/${nameAndVersion.slice(0, atIndex)}`,
72
+ version: nameAndVersion.slice(atIndex + 1),
73
+ };
74
+ }
75
+ return { name: `${scope}/${nameAndVersion}` };
76
+ }
77
+ }
78
+
79
+ // Handle non-scoped packages
80
+ const atIndex = spec.lastIndexOf('@');
81
+ if (atIndex > 0) {
82
+ return {
83
+ name: spec.slice(0, atIndex),
84
+ version: spec.slice(atIndex + 1),
85
+ };
86
+ }
87
+ return { name: spec };
88
+ }
89
+
90
+ /**
91
+ * Convert a package specifier to an esm.sh URL
92
+ *
93
+ * @param packageName - The npm package name
94
+ * @param version - Optional version specifier
95
+ * @param subpath - Optional subpath (e.g., '/client' for 'react-dom/client')
96
+ * @param deps - Optional dependency version overrides (use ?deps=react@18)
97
+ */
98
+ export function toEsmShUrl(
99
+ packageName: string,
100
+ version?: string,
101
+ subpath?: string,
102
+ deps?: Record<string, string>,
103
+ ): string {
104
+ let url = `${cdnBaseUrl}/${packageName}`;
105
+
106
+ if (version) {
107
+ url += `@${version}`;
108
+ }
109
+
110
+ if (subpath) {
111
+ url += `/${subpath}`;
112
+ }
113
+
114
+ // Add deps flag to ensure consistent dependency versions across all packages
115
+ // This makes all packages use the same React version, avoiding version mismatches
116
+ if (deps && Object.keys(deps).length > 0) {
117
+ const depsStr = Object.entries(deps)
118
+ .map(([name, ver]) => `${name}@${ver}`)
119
+ .join(',');
120
+ url += `?deps=${depsStr}`;
121
+ }
122
+
123
+ return url;
124
+ }
125
+
126
+ /**
127
+ * Check if an import path is a bare module specifier
128
+ */
129
+ export function isBareImport(path: string): boolean {
130
+ // Not bare if starts with ., /, or is a URL
131
+ if (
132
+ path.startsWith('.') ||
133
+ path.startsWith('/') ||
134
+ path.startsWith('http://') ||
135
+ path.startsWith('https://')
136
+ ) {
137
+ return false;
138
+ }
139
+ return true;
140
+ }
141
+
142
+ /**
143
+ * Extract package name and subpath from an import
144
+ */
145
+ export function parseImportPath(importPath: string): {
146
+ packageName: string;
147
+ subpath?: string;
148
+ } {
149
+ // Handle scoped packages
150
+ if (importPath.startsWith('@')) {
151
+ const parts = importPath.split('/');
152
+ if (parts.length >= 2) {
153
+ const packageName = `${parts[0]}/${parts[1]}`;
154
+ const subpath = parts.slice(2).join('/');
155
+ return { packageName, subpath: subpath || undefined };
156
+ }
157
+ }
158
+
159
+ // Handle non-scoped packages
160
+ const parts = importPath.split('/');
161
+ const packageName = parts[0] as string;
162
+ const subpath = parts.slice(1).join('/');
163
+ return { packageName, subpath: subpath || undefined };
164
+ }
165
+
166
+ export interface CdnTransformOptions {
167
+ /** Map of package names to versions */
168
+ packages?: Record<string, string>;
169
+ /** Additional external packages */
170
+ external?: string[];
171
+ /** Use bundled versions from esm.sh (adds ?bundle) */
172
+ bundle?: boolean;
173
+ /** Packages to inject from window globals instead of CDN */
174
+ globals?: Record<string, string>;
175
+ /** Dependency version overrides for CDN URLs (e.g., { react: '18' }) */
176
+ deps?: Record<string, string>;
177
+ /** Import path aliases (e.g., { '@/components/ui/*': '@packagedcn/react' }) */
178
+ aliases?: Record<string, string>;
179
+ }
180
+
181
+ /**
182
+ * Match an import path against alias patterns
183
+ * Supports glob patterns like '@/components/ui/*'
184
+ */
185
+ function matchAlias(
186
+ importPath: string,
187
+ aliases: Record<string, string>,
188
+ ): string | null {
189
+ for (const [pattern, target] of Object.entries(aliases)) {
190
+ // Handle glob patterns ending with /*
191
+ if (pattern.endsWith('/*')) {
192
+ const prefix = pattern.slice(0, -2); // Remove /*
193
+ if (importPath === prefix || importPath.startsWith(prefix + '/')) {
194
+ return target;
195
+ }
196
+ }
197
+ // Exact match
198
+ if (importPath === pattern) {
199
+ return target;
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+
205
+ /**
206
+ * Create an esbuild plugin that transforms bare imports to CDN URLs
207
+ * and injects globals for specified packages (like React)
208
+ */
209
+ export function cdnTransformPlugin(options: CdnTransformOptions = {}): Plugin {
210
+ const {
211
+ packages = {},
212
+ external = [],
213
+ bundle = false,
214
+ globals = {},
215
+ deps = {},
216
+ aliases = {},
217
+ } = options;
218
+
219
+ const externalSet = new Set([...EXTERNAL_PACKAGES, ...external]);
220
+ const globalsSet = new Set(Object.keys(globals));
221
+
222
+ return {
223
+ name: 'cdn-transform',
224
+ setup(build) {
225
+ // Handle import aliases first (e.g., @/components/ui/* -> @packagedcn/react)
226
+ // This must resolve directly to CDN URL or global-inject
227
+ build.onResolve({ filter: /.*/ }, (args) => {
228
+ const aliasTarget = matchAlias(args.path, aliases);
229
+ if (aliasTarget) {
230
+ const { packageName, subpath } = parseImportPath(aliasTarget);
231
+
232
+ // Check if aliased target should use globals
233
+ if (globalsSet.has(packageName)) {
234
+ return {
235
+ path: aliasTarget,
236
+ namespace: 'global-inject',
237
+ };
238
+ }
239
+
240
+ // Convert aliased import directly to CDN URL
241
+ const version = packages[packageName];
242
+ let url = toEsmShUrl(
243
+ packageName,
244
+ version,
245
+ subpath,
246
+ Object.keys(deps).length > 0 ? deps : undefined,
247
+ );
248
+ if (bundle) {
249
+ url += url.includes('?') ? '&bundle' : '?bundle';
250
+ }
251
+
252
+ return {
253
+ path: url,
254
+ external: true,
255
+ };
256
+ }
257
+ return null;
258
+ });
259
+
260
+ // Handle packages that should come from window globals
261
+ build.onResolve({ filter: /.*/ }, (args) => {
262
+ if (!isBareImport(args.path)) {
263
+ return null;
264
+ }
265
+
266
+ const { packageName } = parseImportPath(args.path);
267
+
268
+ // Check if this package should be injected from globals
269
+ if (globalsSet.has(packageName)) {
270
+ return {
271
+ path: args.path,
272
+ namespace: 'global-inject',
273
+ };
274
+ }
275
+
276
+ return null;
277
+ });
278
+
279
+ // Provide virtual modules that export window globals
280
+ build.onLoad({ filter: /.*/, namespace: 'global-inject' }, (args) => {
281
+ const { packageName, subpath } = parseImportPath(args.path);
282
+ const globalName = globals[packageName];
283
+
284
+ if (!globalName) return null;
285
+
286
+ // Handle subpath imports like 'react-dom/client'
287
+ if (subpath) {
288
+ // For react-dom/client, we need to access window.ReactDOM (which is already the client)
289
+ return {
290
+ contents: `export * from '${packageName}'; export { default } from '${packageName}';`,
291
+ loader: 'js',
292
+ };
293
+ }
294
+
295
+ // Generate a module that exports the global
296
+ // This handles both default and named exports
297
+ const contents = `
298
+ const mod = window.${globalName};
299
+ export default mod;
300
+ // Re-export all properties as named exports
301
+ const { ${getCommonExports(packageName).join(', ')} } = mod;
302
+ export { ${getCommonExports(packageName).join(', ')} };
303
+ `;
304
+ return {
305
+ contents,
306
+ loader: 'js',
307
+ };
308
+ });
309
+
310
+ // Mark external packages and transform to CDN URLs
311
+ build.onResolve({ filter: /.*/ }, (args) => {
312
+ if (!isBareImport(args.path)) {
313
+ return null; // Let esbuild handle relative/absolute imports
314
+ }
315
+
316
+ // Check if it's a Node.js builtin
317
+ if (NODE_BUILTINS.has(args.path)) {
318
+ return { external: true };
319
+ }
320
+
321
+ const { packageName, subpath } = parseImportPath(args.path);
322
+
323
+ // Skip if handled by globals
324
+ if (globalsSet.has(packageName)) {
325
+ return null;
326
+ }
327
+
328
+ // Check if it should be external (but not converted to CDN)
329
+ if (externalSet.has(packageName)) {
330
+ return { external: true };
331
+ }
332
+
333
+ // Get version from packages map
334
+ const version = packages[packageName];
335
+
336
+ // Use deps from options for consistent dependency versions across CDN packages
337
+ // This prevents multiple React instances which cause element serialization errors
338
+ let url = toEsmShUrl(
339
+ packageName,
340
+ version,
341
+ subpath,
342
+ Object.keys(deps).length > 0 ? deps : undefined,
343
+ );
344
+ if (bundle) {
345
+ url += url.includes('?') ? '&bundle' : '?bundle';
346
+ }
347
+
348
+ return {
349
+ path: url,
350
+ external: true,
351
+ };
352
+ });
353
+ },
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Get common named exports for known packages
359
+ */
360
+ function getCommonExports(packageName: string): string[] {
361
+ const exports: Record<string, string[]> = {
362
+ react: [
363
+ 'useState',
364
+ 'useEffect',
365
+ 'useCallback',
366
+ 'useMemo',
367
+ 'useRef',
368
+ 'useContext',
369
+ 'useReducer',
370
+ 'useLayoutEffect',
371
+ 'useId',
372
+ 'createContext',
373
+ 'createElement',
374
+ 'cloneElement',
375
+ 'createRef',
376
+ 'forwardRef',
377
+ 'lazy',
378
+ 'memo',
379
+ 'Fragment',
380
+ 'Suspense',
381
+ 'StrictMode',
382
+ 'Component',
383
+ 'PureComponent',
384
+ 'Children',
385
+ 'isValidElement',
386
+ ],
387
+ 'react-dom': [
388
+ 'createPortal',
389
+ 'flushSync',
390
+ 'render',
391
+ 'hydrate',
392
+ 'unmountComponentAtNode',
393
+ ],
394
+ };
395
+ return exports[packageName] || [];
396
+ }
397
+
398
+ /**
399
+ * Generate import map for CDN dependencies
400
+ */
401
+ export function generateImportMap(
402
+ packages: Record<string, string>,
403
+ ): Record<string, string> {
404
+ const imports: Record<string, string> = {};
405
+
406
+ for (const [name, version] of Object.entries(packages)) {
407
+ imports[name] = toEsmShUrl(name, version);
408
+ }
409
+
410
+ return imports;
411
+ }
@@ -0,0 +1,4 @@
1
+ export { cdnTransformPlugin, generateImportMap } from './cdn.js';
2
+ export type { CdnTransformOptions } from './cdn.js';
3
+ export { vfsPlugin } from './vfs.js';
4
+ export type { VFSPluginOptions } from './vfs.js';
@@ -0,0 +1,138 @@
1
+ import type { Plugin, Loader } from 'esbuild-wasm';
2
+ import type { VirtualProject } from '../vfs/types.js';
3
+
4
+ function dirname(path: string): string {
5
+ const idx = path.lastIndexOf('/');
6
+ return idx === -1 ? '.' : path.slice(0, idx) || '.';
7
+ }
8
+
9
+ function normalizePath(path: string): string {
10
+ const parts: string[] = [];
11
+ for (const segment of path.split('/')) {
12
+ if (segment === '..') parts.pop();
13
+ else if (segment && segment !== '.') parts.push(segment);
14
+ }
15
+ return parts.join('/');
16
+ }
17
+
18
+ function inferLoader(path: string, language?: string): Loader {
19
+ if (language) {
20
+ switch (language) {
21
+ case 'typescript':
22
+ case 'ts':
23
+ return 'ts';
24
+ case 'tsx':
25
+ return 'tsx';
26
+ case 'javascript':
27
+ case 'js':
28
+ return 'js';
29
+ case 'jsx':
30
+ return 'jsx';
31
+ case 'json':
32
+ return 'json';
33
+ case 'css':
34
+ return 'css';
35
+ }
36
+ }
37
+ const ext = path.split('.').pop();
38
+ switch (ext) {
39
+ case 'ts':
40
+ return 'ts';
41
+ case 'tsx':
42
+ return 'tsx';
43
+ case 'js':
44
+ return 'js';
45
+ case 'jsx':
46
+ return 'jsx';
47
+ case 'json':
48
+ return 'json';
49
+ case 'css':
50
+ return 'css';
51
+ default:
52
+ return 'tsx';
53
+ }
54
+ }
55
+
56
+ function normalizeVFSPath(path: string): string {
57
+ if (path.startsWith('@/')) {
58
+ return path.slice(2);
59
+ }
60
+ return path;
61
+ }
62
+
63
+ function resolveRelativePath(importer: string, target: string): string {
64
+ const importerDir = dirname(normalizeVFSPath(importer));
65
+ const combined = importerDir === '.' ? target : `${importerDir}/${target}`;
66
+ return normalizePath(combined);
67
+ }
68
+
69
+ function matchAlias(
70
+ importPath: string,
71
+ aliases?: Record<string, string>,
72
+ ): string | null {
73
+ if (!aliases) return null;
74
+ for (const [pattern, target] of Object.entries(aliases)) {
75
+ if (pattern.endsWith('/*')) {
76
+ const prefix = pattern.slice(0, -2);
77
+ if (importPath === prefix || importPath.startsWith(prefix + '/')) {
78
+ return target;
79
+ }
80
+ }
81
+ if (importPath === pattern) {
82
+ return target;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function findFile(project: VirtualProject, path: string): string | null {
89
+ if (project.files.has(path)) return path;
90
+ const extensions = ['.tsx', '.ts', '.jsx', '.js', '.json'];
91
+ for (const ext of extensions) {
92
+ if (project.files.has(path + ext)) return path + ext;
93
+ }
94
+ for (const ext of extensions) {
95
+ const indexPath = `${path}/index${ext}`;
96
+ if (project.files.has(indexPath)) return indexPath;
97
+ }
98
+ return null;
99
+ }
100
+
101
+ export interface VFSPluginOptions {
102
+ aliases?: Record<string, string>;
103
+ }
104
+
105
+ export function vfsPlugin(
106
+ project: VirtualProject,
107
+ options: VFSPluginOptions = {},
108
+ ): Plugin {
109
+ return {
110
+ name: 'patchwork-vfs',
111
+ setup(build) {
112
+ build.onResolve({ filter: /^@\// }, (args) => {
113
+ const aliased = matchAlias(args.path, options.aliases);
114
+ if (aliased) return null;
115
+ return { path: args.path, namespace: 'vfs' };
116
+ });
117
+
118
+ build.onResolve({ filter: /^\./ }, (args) => {
119
+ if (args.namespace !== 'vfs') return null;
120
+ const resolved = resolveRelativePath(args.importer, args.path);
121
+ return { path: resolved, namespace: 'vfs' };
122
+ });
123
+
124
+ build.onLoad({ filter: /.*/, namespace: 'vfs' }, (args) => {
125
+ const normalPath = normalizeVFSPath(args.path);
126
+ const filePath = findFile(project, normalPath);
127
+ if (!filePath) {
128
+ throw new Error(`File not found in VFS: ${args.path}`);
129
+ }
130
+ const file = project.files.get(filePath)!;
131
+ return {
132
+ contents: file.content,
133
+ loader: inferLoader(filePath, file.language),
134
+ };
135
+ });
136
+ },
137
+ };
138
+ }