@designtools/next-plugin 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/chunk-6DZX6EAA.mjs +37 -0
- package/dist/codesurface-mount-loader.js +6 -4
- package/dist/codesurface-mount-loader.mjs +6 -4
- package/dist/codesurface.d.mts +3 -1
- package/dist/codesurface.d.ts +3 -1
- package/dist/codesurface.js +192 -12
- package/dist/codesurface.mjs +193 -13
- package/dist/index.js +75 -162
- package/dist/index.mjs +75 -162
- package/package.json +2 -2
- package/src/codesurface-mount-loader.ts +10 -6
- package/src/codesurface.tsx +254 -25
- package/src/index.ts +72 -12
- package/src/preview-route.ts +33 -176
package/src/preview-route.ts
CHANGED
|
@@ -1,51 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Component registry generator for component isolation.
|
|
3
3
|
*
|
|
4
|
-
* Writes a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Writes a static import map file into the target app's app directory.
|
|
5
|
+
* The <CodeSurface /> component uses this registry to dynamically load
|
|
6
|
+
* components for the isolation overlay — no route change needed.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from "fs";
|
|
10
10
|
import path from "path";
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const REGISTRY_FILE = "designtools-registry.ts";
|
|
13
13
|
|
|
14
|
-
/** Generate the
|
|
15
|
-
export function
|
|
14
|
+
/** Generate the component registry file in the target app's app directory. */
|
|
15
|
+
export function generateComponentRegistry(appDir: string): void {
|
|
16
16
|
const projectRoot = path.dirname(appDir);
|
|
17
|
-
const previewDir = path.join(appDir, PREVIEW_DIR);
|
|
18
17
|
|
|
19
18
|
// Discover component files so we can generate static imports
|
|
20
19
|
const componentPaths = discoverComponentFiles(projectRoot);
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const registryEntries = componentPaths
|
|
22
|
+
.map((p) => ` "${p}": () => import("@/${p}"),`)
|
|
23
|
+
.join("\n");
|
|
24
|
+
|
|
25
|
+
const content = `"use client";
|
|
26
|
+
// Auto-generated by @designtools/next-plugin — do not edit
|
|
27
|
+
|
|
28
|
+
const COMPONENT_REGISTRY: Record<string, () => Promise<any>> = {
|
|
29
|
+
${registryEntries}
|
|
30
|
+
};
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
// Self-register on window so CodeSurface can access it without
|
|
33
|
+
// passing functions through the RSC serialization boundary.
|
|
34
|
+
if (typeof window !== "undefined") {
|
|
35
|
+
(window as any).__DESIGNTOOLS_REGISTRY__ = COMPONENT_REGISTRY;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
fs.writeFileSync(
|
|
34
|
-
path.join(previewDir, "page.tsx"),
|
|
35
|
-
getPageTemplate(componentPaths),
|
|
36
|
-
"utf-8"
|
|
37
|
-
);
|
|
39
|
+
fs.writeFileSync(path.join(appDir, REGISTRY_FILE), content, "utf-8");
|
|
38
40
|
|
|
39
|
-
// Ensure
|
|
41
|
+
// Ensure registry file is gitignored
|
|
40
42
|
ensureGitignore(projectRoot);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
/** Clean up generated
|
|
44
|
-
export function
|
|
45
|
-
const
|
|
45
|
+
/** Clean up generated registry file. */
|
|
46
|
+
export function cleanupComponentRegistry(appDir: string): void {
|
|
47
|
+
const registryPath = path.join(appDir, REGISTRY_FILE);
|
|
46
48
|
try {
|
|
47
|
-
fs.
|
|
48
|
-
// Directory itself is removed by rmSync above
|
|
49
|
+
fs.unlinkSync(registryPath);
|
|
49
50
|
} catch {
|
|
50
51
|
// ignore
|
|
51
52
|
}
|
|
@@ -53,7 +54,7 @@ export function cleanupPreviewRoute(appDir: string): void {
|
|
|
53
54
|
|
|
54
55
|
function ensureGitignore(projectRoot: string): void {
|
|
55
56
|
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
56
|
-
const entry = "app/designtools-
|
|
57
|
+
const entry = "app/designtools-registry.ts";
|
|
57
58
|
|
|
58
59
|
try {
|
|
59
60
|
const existing = fs.existsSync(gitignorePath)
|
|
@@ -70,9 +71,10 @@ function ensureGitignore(projectRoot: string): void {
|
|
|
70
71
|
/**
|
|
71
72
|
* Scan the project for component .tsx files in known directories.
|
|
72
73
|
* Returns paths like "components/ui/button" (no extension).
|
|
74
|
+
* Accepts an optional override directory to skip hardcoded candidates.
|
|
73
75
|
*/
|
|
74
|
-
function discoverComponentFiles(projectRoot: string): string[] {
|
|
75
|
-
const dirs = ["components/ui", "src/components/ui"];
|
|
76
|
+
function discoverComponentFiles(projectRoot: string, overrideDir?: string): string[] {
|
|
77
|
+
const dirs = overrideDir ? [overrideDir] : ["components/ui", "src/components/ui"];
|
|
76
78
|
for (const dir of dirs) {
|
|
77
79
|
const fullDir = path.join(projectRoot, dir);
|
|
78
80
|
if (fs.existsSync(fullDir)) {
|
|
@@ -84,148 +86,3 @@ function discoverComponentFiles(projectRoot: string): string[] {
|
|
|
84
86
|
}
|
|
85
87
|
return [];
|
|
86
88
|
}
|
|
87
|
-
|
|
88
|
-
function getLayoutTemplate(): string {
|
|
89
|
-
return `// Auto-generated by @designtools/next-plugin — do not edit
|
|
90
|
-
export default function PreviewLayout({ children }: { children: React.ReactNode }) {
|
|
91
|
-
return (
|
|
92
|
-
<div style={{ padding: 32, background: "var(--background, #fff)", minHeight: "100vh" }}>
|
|
93
|
-
{children}
|
|
94
|
-
</div>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function getPageTemplate(componentPaths: string[]): string {
|
|
101
|
-
// Generate static import map entries — webpack can analyze these
|
|
102
|
-
const registryEntries = componentPaths
|
|
103
|
-
.map((p) => ` "${p}": () => import("@/${p}"),`)
|
|
104
|
-
.join("\n");
|
|
105
|
-
|
|
106
|
-
return `// Auto-generated by @designtools/next-plugin — do not edit
|
|
107
|
-
"use client";
|
|
108
|
-
|
|
109
|
-
import { useState, useEffect, useCallback, createElement } from "react";
|
|
110
|
-
|
|
111
|
-
/* Static import registry — webpack can analyze these imports */
|
|
112
|
-
const COMPONENT_REGISTRY: Record<string, () => Promise<any>> = {
|
|
113
|
-
${registryEntries}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
interface Combination {
|
|
117
|
-
label: string;
|
|
118
|
-
props: Record<string, string>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface RenderMsg {
|
|
122
|
-
type: "tool:renderPreview";
|
|
123
|
-
componentPath: string;
|
|
124
|
-
exportName: string;
|
|
125
|
-
combinations: Combination[];
|
|
126
|
-
defaultChildren: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export default function PreviewPage() {
|
|
130
|
-
const [Component, setComponent] = useState<React.ComponentType<any> | null>(null);
|
|
131
|
-
const [combinations, setCombinations] = useState<Combination[]>([]);
|
|
132
|
-
const [defaultChildren, setDefaultChildren] = useState("");
|
|
133
|
-
const [error, setError] = useState<string | null>(null);
|
|
134
|
-
|
|
135
|
-
const handleMessage = useCallback(async (e: MessageEvent) => {
|
|
136
|
-
const msg = e.data;
|
|
137
|
-
if (msg?.type !== "tool:renderPreview") return;
|
|
138
|
-
|
|
139
|
-
const { componentPath, exportName, combinations: combos, defaultChildren: children } = msg as RenderMsg;
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
setError(null);
|
|
143
|
-
setCombinations(combos);
|
|
144
|
-
setDefaultChildren(children || exportName);
|
|
145
|
-
|
|
146
|
-
const loader = COMPONENT_REGISTRY[componentPath];
|
|
147
|
-
if (!loader) {
|
|
148
|
-
setError(\`Component "\${componentPath}" not found in registry. Available: \${Object.keys(COMPONENT_REGISTRY).join(", ")}\`);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const mod = await loader();
|
|
153
|
-
const Comp = mod[exportName] || mod.default;
|
|
154
|
-
if (!Comp) {
|
|
155
|
-
setError(\`Export "\${exportName}" not found in \${componentPath}\`);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
setComponent(() => Comp);
|
|
160
|
-
|
|
161
|
-
// Notify editor that preview is ready
|
|
162
|
-
window.parent.postMessage(
|
|
163
|
-
{ type: "tool:previewReady", cellCount: combos.length },
|
|
164
|
-
"*"
|
|
165
|
-
);
|
|
166
|
-
} catch (err: any) {
|
|
167
|
-
setError(\`Failed to load component: \${err.message}\`);
|
|
168
|
-
}
|
|
169
|
-
}, []);
|
|
170
|
-
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
window.addEventListener("message", handleMessage);
|
|
173
|
-
// Signal readiness to the editor
|
|
174
|
-
window.parent.postMessage({ type: "tool:injectedReady" }, "*");
|
|
175
|
-
return () => window.removeEventListener("message", handleMessage);
|
|
176
|
-
}, [handleMessage]);
|
|
177
|
-
|
|
178
|
-
if (error) {
|
|
179
|
-
return (
|
|
180
|
-
<div style={{ padding: 32, color: "#ef4444", fontFamily: "monospace", fontSize: 14 }}>
|
|
181
|
-
{error}
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!Component) {
|
|
187
|
-
return (
|
|
188
|
-
<div style={{ padding: 32, color: "#888", fontFamily: "system-ui", fontSize: 14 }}>
|
|
189
|
-
Waiting for component…
|
|
190
|
-
</div>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<div style={{ fontFamily: "system-ui" }}>
|
|
196
|
-
<div style={{
|
|
197
|
-
display: "grid",
|
|
198
|
-
gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))",
|
|
199
|
-
gap: 24,
|
|
200
|
-
}}>
|
|
201
|
-
{combinations.map((combo, i) => (
|
|
202
|
-
<div key={i} style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
203
|
-
<div style={{
|
|
204
|
-
fontSize: 11,
|
|
205
|
-
fontWeight: 600,
|
|
206
|
-
color: "#888",
|
|
207
|
-
textTransform: "uppercase",
|
|
208
|
-
letterSpacing: "0.05em",
|
|
209
|
-
}}>
|
|
210
|
-
{combo.label}
|
|
211
|
-
</div>
|
|
212
|
-
<div style={{
|
|
213
|
-
padding: 16,
|
|
214
|
-
border: "1px solid #e5e7eb",
|
|
215
|
-
borderRadius: 8,
|
|
216
|
-
display: "flex",
|
|
217
|
-
alignItems: "center",
|
|
218
|
-
justifyContent: "center",
|
|
219
|
-
minHeight: 64,
|
|
220
|
-
background: "#fff",
|
|
221
|
-
}}>
|
|
222
|
-
{createElement(Component, combo.props, defaultChildren)}
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
))}
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
`;
|
|
231
|
-
}
|