@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,233 @@
1
+ import type { VirtualProject } from './vfs/types.js';
2
+
3
+ /**
4
+ * Core types for the Patchwork compiler
5
+ */
6
+
7
+ // Platform types
8
+ export type Platform = 'browser' | 'cli';
9
+
10
+ // Widget manifest
11
+ export interface Manifest {
12
+ name: string;
13
+ version: string;
14
+ description?: string;
15
+ platform: Platform;
16
+ image: string;
17
+ inputs?: Record<string, InputSpec>;
18
+ services?: string[];
19
+ packages?: Record<string, string>;
20
+ }
21
+
22
+ export interface InputSpec {
23
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
24
+ default?: unknown;
25
+ required?: boolean;
26
+ description?: string;
27
+ }
28
+
29
+ // Compiler options (reserved for future use)
30
+ export interface CompileOptions {
31
+ // Entry point and loader are inferred from VirtualProject.entry
32
+ }
33
+
34
+ // Compiled widget output
35
+ export interface CompiledWidget {
36
+ /** Compiled ESM code */
37
+ code: string;
38
+ /** Content hash for caching */
39
+ hash: string;
40
+ /** Original manifest */
41
+ manifest: Manifest;
42
+ /** Source map (if generated) */
43
+ sourceMap?: string;
44
+ }
45
+
46
+ // Mount options
47
+ export type MountMode = 'embedded' | 'iframe';
48
+
49
+ export interface MountOptions {
50
+ /** Target DOM element to mount into */
51
+ target: HTMLElement;
52
+ /** Mount mode: embedded (trusted) or iframe (sandboxed) */
53
+ mode: MountMode;
54
+ /** CSP sandbox attributes for iframe mode */
55
+ sandbox?: string[];
56
+ /** Initial props/inputs to pass to widget */
57
+ inputs?: Record<string, unknown>;
58
+ }
59
+
60
+ // Mounted widget handle
61
+ export interface MountedWidget {
62
+ /** Unique mount ID */
63
+ id: string;
64
+ /** The compiled widget */
65
+ widget: CompiledWidget;
66
+ /** Mount mode used */
67
+ mode: MountMode;
68
+ /** Target element */
69
+ target: HTMLElement;
70
+ /** Iframe element (if mode is 'iframe') */
71
+ iframe?: HTMLIFrameElement;
72
+ /** Inputs used for the mount (if provided) */
73
+ inputs?: Record<string, unknown>;
74
+ /** Sandbox attributes used for iframe mode (if provided) */
75
+ sandbox?: string[];
76
+ /** Unmount function */
77
+ unmount: () => void;
78
+ }
79
+
80
+ // Image package configuration (from package.json patchwork field)
81
+ export interface ImageConfig {
82
+ platform: Platform;
83
+ esbuild?: {
84
+ target?: string;
85
+ format?: 'esm' | 'cjs' | 'iife';
86
+ jsx?: 'automatic' | 'transform' | 'preserve';
87
+ jsxFactory?: string;
88
+ jsxFragment?: string;
89
+ };
90
+ framework?: {
91
+ /** Map of package names to window global names (e.g., { react: 'React' }) */
92
+ globals?: Record<string, string>;
93
+ /** CDN URLs to preload before widget execution */
94
+ preload?: string[];
95
+ /** Dependency version overrides for CDN packages (e.g., { react: '18' }) */
96
+ deps?: Record<string, string>;
97
+ };
98
+ /** Import path aliases (e.g., { '@/components/ui/*': '@packagedcn/react' }) */
99
+ aliases?: Record<string, string>;
100
+ }
101
+
102
+ /**
103
+ * Mount function signature provided by images to handle widget mounting.
104
+ * Takes the widget module exports and mounts it to the container.
105
+ *
106
+ * @param module - The widget module with its exports (default, mount, render, game, etc.)
107
+ * @param container - The DOM element to mount into
108
+ * @param inputs - Props/inputs to pass to the widget
109
+ * @returns A cleanup function to unmount, or void
110
+ */
111
+ export type ImageMountFn = (
112
+ module: Record<string, unknown>,
113
+ container: HTMLElement,
114
+ inputs: Record<string, unknown>,
115
+ ) => void | (() => void) | Promise<void | (() => void)>;
116
+
117
+ // Loaded image
118
+ export interface LoadedImage {
119
+ /** Package name */
120
+ name: string;
121
+ /** Resolved version */
122
+ version: string;
123
+ /** Browser-importable module URL for this image (when loaded from CDN/local HTTP) */
124
+ moduleUrl?: string;
125
+ /** Package configuration */
126
+ config: ImageConfig;
127
+ /** Package dependencies */
128
+ dependencies: Record<string, string>;
129
+ /** Setup function (if available) */
130
+ setup?: (root: HTMLElement) => void | Promise<void>;
131
+ /** CSS content (if available) */
132
+ css?: string;
133
+ /**
134
+ * Mount function to handle widget mounting.
135
+ * Each image defines how it expects widgets to export their entry points.
136
+ * Falls back to default mounting behavior if not provided.
137
+ */
138
+ mount?: ImageMountFn;
139
+ }
140
+
141
+ // Compiler factory options
142
+ export interface CompilerOptions {
143
+ /** Image package to use (e.g., '@aprovan/patchwork-image-shadcnshadcn') */
144
+ image: string;
145
+ /** Backend proxy URL for service calls */
146
+ proxyUrl: string;
147
+ /** Base URL for CDN (default: 'https://esm.sh'). Used for loading image packages. */
148
+ cdnBaseUrl?: string;
149
+ /** Base URL for widget imports (default: same as cdnBaseUrl). Used for transforming imports in widget code. */
150
+ widgetCdnBaseUrl?: string;
151
+ }
152
+
153
+ // Compiler interface
154
+ export interface Compiler {
155
+ /** Pre-load an image package */
156
+ preloadImage(spec: string): Promise<void>;
157
+
158
+ /** Check if an image is loaded */
159
+ isImageLoaded(spec: string): boolean;
160
+
161
+ /** Compile widget source to ESM */
162
+ compile(
163
+ source: string | VirtualProject,
164
+ manifest: Manifest,
165
+ options?: CompileOptions,
166
+ ): Promise<CompiledWidget>;
167
+
168
+ /** Mount a compiled widget to the DOM */
169
+ mount(widget: CompiledWidget, options: MountOptions): Promise<MountedWidget>;
170
+
171
+ /** Unmount a mounted widget */
172
+ unmount(mounted: MountedWidget): void;
173
+
174
+ /** Hot reload a mounted widget */
175
+ reload(
176
+ mounted: MountedWidget,
177
+ source: string | VirtualProject,
178
+ manifest: Manifest,
179
+ ): Promise<void>;
180
+ }
181
+
182
+ /**
183
+ * Service proxy interface - abstracts service calls to backend
184
+ *
185
+ * The compiler provides the interface; actual implementation (e.g., UTCP, MCP)
186
+ * is handled by the runtime/backend.
187
+ */
188
+ export interface ServiceProxy {
189
+ call(namespace: string, procedure: string, args: unknown[]): Promise<unknown>;
190
+ }
191
+
192
+ /**
193
+ * Service call handler - function that handles calls for a specific namespace
194
+ */
195
+ export type ServiceCallHandler = (
196
+ procedure: string,
197
+ args: unknown[],
198
+ ) => Promise<unknown>;
199
+
200
+ /**
201
+ * Global interface definition for code generation
202
+ *
203
+ * Describes available global namespaces and their methods that widgets can call.
204
+ * Used during compilation to generate proper TypeScript declarations.
205
+ */
206
+ export interface GlobalInterfaceDefinition {
207
+ /** Namespace name (e.g., 'git', 'github') */
208
+ name: string;
209
+ /** Methods available on this namespace (supports nested paths like 'repos.list') */
210
+ methods: string[];
211
+ /** Optional TypeScript type definitions for methods */
212
+ types?: string;
213
+ }
214
+
215
+ // Message types for iframe bridge
216
+ export type BridgeMessageType = 'service-call' | 'service-result' | 'error';
217
+
218
+ export interface BridgeMessage {
219
+ type: BridgeMessageType;
220
+ id: string;
221
+ payload: unknown;
222
+ }
223
+
224
+ export interface ServiceCallPayload {
225
+ namespace: string;
226
+ procedure: string;
227
+ args: unknown[];
228
+ }
229
+
230
+ export interface ServiceResultPayload {
231
+ result?: unknown;
232
+ error?: string;
233
+ }
@@ -0,0 +1,66 @@
1
+ import type { StorageBackend } from '../types.js';
2
+
3
+ const DB_NAME = 'patchwork-vfs';
4
+ const STORE_NAME = 'files';
5
+
6
+ function openDB(): Promise<IDBDatabase> {
7
+ return new Promise((resolve, reject) => {
8
+ const request = indexedDB.open(DB_NAME, 1);
9
+ request.onerror = () => reject(request.error);
10
+ request.onsuccess = () => resolve(request.result);
11
+ request.onupgradeneeded = () => {
12
+ request.result.createObjectStore(STORE_NAME);
13
+ };
14
+ });
15
+ }
16
+
17
+ function withStore<T>(
18
+ mode: IDBTransactionMode,
19
+ fn: (store: IDBObjectStore) => IDBRequest<T>,
20
+ ): Promise<T> {
21
+ return openDB().then(
22
+ (db) =>
23
+ new Promise((resolve, reject) => {
24
+ const tx = db.transaction(STORE_NAME, mode);
25
+ const store = tx.objectStore(STORE_NAME);
26
+ const request = fn(store);
27
+ request.onerror = () => reject(request.error);
28
+ request.onsuccess = () => resolve(request.result);
29
+ }),
30
+ );
31
+ }
32
+
33
+ export class IndexedDBBackend implements StorageBackend {
34
+ constructor(private prefix = 'vfs') {}
35
+
36
+ private key(path: string): string {
37
+ return `${this.prefix}:${path}`;
38
+ }
39
+
40
+ async get(path: string): Promise<string | null> {
41
+ const result = await withStore('readonly', (store) =>
42
+ store.get(this.key(path)),
43
+ );
44
+ return result ?? null;
45
+ }
46
+
47
+ async put(path: string, content: string): Promise<void> {
48
+ await withStore('readwrite', (store) => store.put(content, this.key(path)));
49
+ }
50
+
51
+ async delete(path: string): Promise<void> {
52
+ await withStore('readwrite', (store) => store.delete(this.key(path)));
53
+ }
54
+
55
+ async list(prefix?: string): Promise<string[]> {
56
+ const keyPrefix = prefix ? this.key(prefix) : this.key('');
57
+ const allKeys = await withStore('readonly', (store) => store.getAllKeys());
58
+ return (allKeys as string[])
59
+ .filter((k) => k.startsWith(keyPrefix))
60
+ .map((k) => k.slice(this.prefix.length + 1));
61
+ }
62
+
63
+ async exists(path: string): Promise<boolean> {
64
+ return (await this.get(path)) !== null;
65
+ }
66
+ }
@@ -0,0 +1,41 @@
1
+ import type { StorageBackend } from '../types.js';
2
+
3
+ export interface LocalFSConfig {
4
+ baseUrl: string;
5
+ }
6
+
7
+ export class LocalFSBackend implements StorageBackend {
8
+ constructor(private config: LocalFSConfig) {}
9
+
10
+ async get(path: string): Promise<string | null> {
11
+ const res = await fetch(`${this.config.baseUrl}/${path}`);
12
+ if (!res.ok) return null;
13
+ return res.text();
14
+ }
15
+
16
+ async put(path: string, content: string): Promise<void> {
17
+ await fetch(`${this.config.baseUrl}/${path}`, {
18
+ method: 'PUT',
19
+ body: content,
20
+ });
21
+ }
22
+
23
+ async delete(path: string): Promise<void> {
24
+ await fetch(`${this.config.baseUrl}/${path}`, { method: 'DELETE' });
25
+ }
26
+
27
+ async list(prefix?: string): Promise<string[]> {
28
+ const url = prefix
29
+ ? `${this.config.baseUrl}?prefix=${encodeURIComponent(prefix)}`
30
+ : this.config.baseUrl;
31
+ const res = await fetch(url);
32
+ return res.json();
33
+ }
34
+
35
+ async exists(path: string): Promise<boolean> {
36
+ const res = await fetch(`${this.config.baseUrl}/${path}`, {
37
+ method: 'HEAD',
38
+ });
39
+ return res.ok;
40
+ }
41
+ }
@@ -0,0 +1,60 @@
1
+ import type { StorageBackend } from '../types.js';
2
+
3
+ export interface S3Config {
4
+ bucket: string;
5
+ region: string;
6
+ prefix?: string;
7
+ credentials?: { accessKeyId: string; secretAccessKey: string };
8
+ }
9
+
10
+ export class S3Backend implements StorageBackend {
11
+ constructor(private config: S3Config) {}
12
+
13
+ private get baseUrl(): string {
14
+ return `https://${this.config.bucket}.s3.${this.config.region}.amazonaws.com`;
15
+ }
16
+
17
+ private key(path: string): string {
18
+ return this.config.prefix ? `${this.config.prefix}/${path}` : path;
19
+ }
20
+
21
+ async get(path: string): Promise<string | null> {
22
+ const res = await fetch(`${this.baseUrl}/${this.key(path)}`);
23
+ if (!res.ok) return null;
24
+ return res.text();
25
+ }
26
+
27
+ async put(path: string, content: string): Promise<void> {
28
+ await fetch(`${this.baseUrl}/${this.key(path)}`, {
29
+ method: 'PUT',
30
+ body: content,
31
+ headers: { 'Content-Type': 'text/plain' },
32
+ });
33
+ }
34
+
35
+ async delete(path: string): Promise<void> {
36
+ await fetch(`${this.baseUrl}/${this.key(path)}`, { method: 'DELETE' });
37
+ }
38
+
39
+ async list(prefix?: string): Promise<string[]> {
40
+ const listPrefix = prefix ? this.key(prefix) : this.config.prefix || '';
41
+ const res = await fetch(
42
+ `${this.baseUrl}?list-type=2&prefix=${encodeURIComponent(listPrefix)}`,
43
+ );
44
+ const xml = await res.text();
45
+ return this.parseListResponse(xml);
46
+ }
47
+
48
+ async exists(path: string): Promise<boolean> {
49
+ const res = await fetch(`${this.baseUrl}/${this.key(path)}`, {
50
+ method: 'HEAD',
51
+ });
52
+ return res.ok;
53
+ }
54
+
55
+ private parseListResponse(xml: string): string[] {
56
+ const matches = xml.matchAll(/<Key>([^<]+)<\/Key>/g);
57
+ const prefixLen = this.config.prefix ? this.config.prefix.length + 1 : 0;
58
+ return Array.from(matches, (m) => (m[1] ?? '').slice(prefixLen));
59
+ }
60
+ }
@@ -0,0 +1,11 @@
1
+ export type { VirtualFile, VirtualProject, StorageBackend } from './types.js';
2
+ export {
3
+ createProjectFromFiles,
4
+ createSingleFileProject,
5
+ resolveEntry,
6
+ detectMainFile,
7
+ } from './project.js';
8
+ export { VFSStore } from './store.js';
9
+ export { IndexedDBBackend } from './backends/indexeddb.js';
10
+ export { LocalFSBackend, type LocalFSConfig } from './backends/local-fs.js';
11
+ export { S3Backend, type S3Config } from './backends/s3.js';
@@ -0,0 +1,56 @@
1
+ import type { VirtualFile, VirtualProject } from './types.js';
2
+
3
+ export function createProjectFromFiles(
4
+ files: VirtualFile[],
5
+ id = crypto.randomUUID(),
6
+ ): VirtualProject {
7
+ const fileMap = new Map<string, VirtualFile>();
8
+ for (const file of files) {
9
+ fileMap.set(file.path, file);
10
+ }
11
+ return { id, entry: resolveEntry(fileMap), files: fileMap };
12
+ }
13
+
14
+ export function resolveEntry(files: Map<string, VirtualFile>): string {
15
+ const paths = Array.from(files.keys());
16
+
17
+ const mainFile = paths.find((p) => /\bmain\.(tsx?|jsx?)$/.test(p));
18
+ if (mainFile) return mainFile;
19
+
20
+ const indexFile = paths.find((p) => /\bindex\.(tsx?|jsx?)$/.test(p));
21
+ if (indexFile) return indexFile;
22
+
23
+ const firstTsx = paths.find((p) => /\.(tsx|jsx)$/.test(p));
24
+ if (firstTsx) return firstTsx;
25
+
26
+ return paths[0] || 'main.tsx';
27
+ }
28
+
29
+ export function detectMainFile(language?: string): string {
30
+ switch (language) {
31
+ case 'tsx':
32
+ case 'typescript':
33
+ return 'main.tsx';
34
+ case 'jsx':
35
+ case 'javascript':
36
+ return 'main.jsx';
37
+ case 'ts':
38
+ return 'main.ts';
39
+ case 'js':
40
+ return 'main.js';
41
+ default:
42
+ return 'main.tsx';
43
+ }
44
+ }
45
+
46
+ export function createSingleFileProject(
47
+ content: string,
48
+ entry = 'main.tsx',
49
+ id = 'inline',
50
+ ): VirtualProject {
51
+ return {
52
+ id,
53
+ entry,
54
+ files: new Map([[entry, { path: entry, content }]]),
55
+ };
56
+ }
@@ -0,0 +1,53 @@
1
+ import type { StorageBackend, VirtualFile, VirtualProject } from './types.js';
2
+ import { resolveEntry } from './project.js';
3
+
4
+ export class VFSStore {
5
+ constructor(private backend: StorageBackend, private root = '') {}
6
+
7
+ private key(path: string): string {
8
+ return this.root ? `${this.root}/${path}` : path;
9
+ }
10
+
11
+ async getFile(path: string): Promise<VirtualFile | null> {
12
+ const content = await this.backend.get(this.key(path));
13
+ if (!content) return null;
14
+ return { path, content };
15
+ }
16
+
17
+ async putFile(file: VirtualFile): Promise<void> {
18
+ await this.backend.put(this.key(file.path), file.content);
19
+ }
20
+
21
+ async deleteFile(path: string): Promise<void> {
22
+ await this.backend.delete(this.key(path));
23
+ }
24
+
25
+ async listFiles(prefix?: string): Promise<string[]> {
26
+ const fullPrefix = prefix ? this.key(prefix) : this.root;
27
+ const paths = await this.backend.list(fullPrefix);
28
+ return paths.map((p) => (this.root ? p.slice(this.root.length + 1) : p));
29
+ }
30
+
31
+ async loadProject(id: string): Promise<VirtualProject | null> {
32
+ const paths = await this.listFiles(id);
33
+ if (paths.length === 0) return null;
34
+
35
+ const files = new Map<string, VirtualFile>();
36
+ await Promise.all(
37
+ paths.map(async (path) => {
38
+ const file = await this.getFile(path);
39
+ if (file) files.set(path.slice(id.length + 1), file);
40
+ }),
41
+ );
42
+
43
+ return { id, entry: resolveEntry(files), files };
44
+ }
45
+
46
+ async saveProject(project: VirtualProject): Promise<void> {
47
+ await Promise.all(
48
+ Array.from(project.files.values()).map((file) =>
49
+ this.putFile({ ...file, path: `${project.id}/${file.path}` }),
50
+ ),
51
+ );
52
+ }
53
+ }
@@ -0,0 +1,20 @@
1
+ export interface VirtualFile {
2
+ path: string;
3
+ content: string;
4
+ language?: string;
5
+ note?: string;
6
+ }
7
+
8
+ export interface VirtualProject {
9
+ id: string;
10
+ entry: string;
11
+ files: Map<string, VirtualFile>;
12
+ }
13
+
14
+ export interface StorageBackend {
15
+ get(path: string): Promise<string | null>;
16
+ put(path: string, content: string): Promise<void>;
17
+ delete(path: string): Promise<void>;
18
+ list(prefix?: string): Promise<string[]>;
19
+ exists(path: string): Promise<boolean>;
20
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"]
8
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ target: 'node20',
7
+ clean: true,
8
+ dts: true,
9
+ splitting: false,
10
+ sourcemap: true,
11
+ shims: true,
12
+ external: ['react', 'react-dom', 'ink'],
13
+ skipNodeModulesBundle: true,
14
+ });
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/jiti.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/jiti.js" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsc" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../tsup/dist/cli-default.js" "$@"
15
+ else
16
+ exec node "$basedir/../tsup/dist/cli-default.js" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../tsup/dist/cli-node.js" "$@"
15
+ else
16
+ exec node "$basedir/../tsup/dist/cli-node.js" "$@"
17
+ fi