@designtools/next-plugin 0.1.2 → 0.1.6
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/dist/codesurface.js +357 -0
- package/dist/codesurface.mjs +357 -0
- package/dist/index.js +197 -4
- package/dist/index.mjs +197 -4
- package/package.json +1 -1
- package/src/codesurface.tsx +476 -0
- package/src/index.ts +11 -0
- package/src/preview-route.ts +231 -0
- package/dist/codecanvas-mount-loader.d.mts +0 -15
- package/dist/codecanvas-mount-loader.d.ts +0 -15
- package/dist/codecanvas-mount-loader.js +0 -51
- package/dist/codecanvas-mount-loader.mjs +0 -32
- package/dist/codecanvas.d.mts +0 -3
- package/dist/codecanvas.d.ts +0 -3
- package/dist/codecanvas.js +0 -426
- package/dist/codecanvas.mjs +0 -403
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview route generator for component isolation.
|
|
3
|
+
*
|
|
4
|
+
* Writes a catch-all preview page into the target app's app directory
|
|
5
|
+
* that can render any component with arbitrary props via postMessage.
|
|
6
|
+
* Uses Next.js file-system routing — no custom server needed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
const PREVIEW_DIR = "designtools-preview";
|
|
13
|
+
|
|
14
|
+
/** Generate the preview route files in the target app's app directory. */
|
|
15
|
+
export function generatePreviewRoute(appDir: string): void {
|
|
16
|
+
const projectRoot = path.dirname(appDir);
|
|
17
|
+
const previewDir = path.join(appDir, PREVIEW_DIR);
|
|
18
|
+
|
|
19
|
+
// Discover component files so we can generate static imports
|
|
20
|
+
const componentPaths = discoverComponentFiles(projectRoot);
|
|
21
|
+
|
|
22
|
+
// Create directory
|
|
23
|
+
fs.mkdirSync(previewDir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
// Write layout — minimal shell, no app chrome
|
|
26
|
+
fs.writeFileSync(
|
|
27
|
+
path.join(previewDir, "layout.tsx"),
|
|
28
|
+
getLayoutTemplate(),
|
|
29
|
+
"utf-8"
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Write page — client component that renders previews via postMessage
|
|
33
|
+
fs.writeFileSync(
|
|
34
|
+
path.join(previewDir, "page.tsx"),
|
|
35
|
+
getPageTemplate(componentPaths),
|
|
36
|
+
"utf-8"
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Ensure designtools-preview is gitignored
|
|
40
|
+
ensureGitignore(projectRoot);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Clean up generated preview files. */
|
|
44
|
+
export function cleanupPreviewRoute(appDir: string): void {
|
|
45
|
+
const previewDir = path.join(appDir, PREVIEW_DIR);
|
|
46
|
+
try {
|
|
47
|
+
fs.rmSync(previewDir, { recursive: true, force: true });
|
|
48
|
+
// Directory itself is removed by rmSync above
|
|
49
|
+
} catch {
|
|
50
|
+
// ignore
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ensureGitignore(projectRoot: string): void {
|
|
55
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
56
|
+
const entry = "app/designtools-preview";
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const existing = fs.existsSync(gitignorePath)
|
|
60
|
+
? fs.readFileSync(gitignorePath, "utf-8")
|
|
61
|
+
: "";
|
|
62
|
+
if (!existing.includes(entry)) {
|
|
63
|
+
fs.appendFileSync(gitignorePath, `\n# Generated by @designtools/next-plugin\n${entry}\n`);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// ignore if we can't write gitignore
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Scan the project for component .tsx files in known directories.
|
|
72
|
+
* Returns paths like "components/ui/button" (no extension).
|
|
73
|
+
*/
|
|
74
|
+
function discoverComponentFiles(projectRoot: string): string[] {
|
|
75
|
+
const dirs = ["components/ui", "src/components/ui"];
|
|
76
|
+
for (const dir of dirs) {
|
|
77
|
+
const fullDir = path.join(projectRoot, dir);
|
|
78
|
+
if (fs.existsSync(fullDir)) {
|
|
79
|
+
const files = fs.readdirSync(fullDir);
|
|
80
|
+
return files
|
|
81
|
+
.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts") || f.endsWith(".jsx"))
|
|
82
|
+
.map((f) => `${dir}/${f.replace(/\.(tsx|ts|jsx|js)$/, "")}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
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
|
+
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Webpack loader that auto-mounts <CodeCanvas /> in the root layout.
|
|
3
|
-
* Only runs in development. Injects the import and component into the JSX.
|
|
4
|
-
*
|
|
5
|
-
* Strategy: Simple string injection — find the {children} pattern in the layout
|
|
6
|
-
* and add <CodeCanvas /> alongside it.
|
|
7
|
-
*/
|
|
8
|
-
interface LoaderContext {
|
|
9
|
-
resourcePath: string;
|
|
10
|
-
callback(err: Error | null, content?: string): void;
|
|
11
|
-
async(): (err: Error | null, content?: string) => void;
|
|
12
|
-
}
|
|
13
|
-
declare function codecanvasMountLoader(this: LoaderContext, source: string): void;
|
|
14
|
-
|
|
15
|
-
export { codecanvasMountLoader as default };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Webpack loader that auto-mounts <CodeCanvas /> in the root layout.
|
|
3
|
-
* Only runs in development. Injects the import and component into the JSX.
|
|
4
|
-
*
|
|
5
|
-
* Strategy: Simple string injection — find the {children} pattern in the layout
|
|
6
|
-
* and add <CodeCanvas /> alongside it.
|
|
7
|
-
*/
|
|
8
|
-
interface LoaderContext {
|
|
9
|
-
resourcePath: string;
|
|
10
|
-
callback(err: Error | null, content?: string): void;
|
|
11
|
-
async(): (err: Error | null, content?: string) => void;
|
|
12
|
-
}
|
|
13
|
-
declare function codecanvasMountLoader(this: LoaderContext, source: string): void;
|
|
14
|
-
|
|
15
|
-
export { codecanvasMountLoader as default };
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/codecanvas-mount-loader.ts
|
|
21
|
-
var codecanvas_mount_loader_exports = {};
|
|
22
|
-
__export(codecanvas_mount_loader_exports, {
|
|
23
|
-
default: () => codecanvasMountLoader
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(codecanvas_mount_loader_exports);
|
|
26
|
-
function codecanvasMountLoader(source) {
|
|
27
|
-
const callback = this.async();
|
|
28
|
-
if (!source.includes("<html")) {
|
|
29
|
-
callback(null, source);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (source.includes("CodeCanvas")) {
|
|
33
|
-
callback(null, source);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const importStatement = `import { CodeCanvas } from "@designtools/next-plugin/codecanvas";
|
|
37
|
-
`;
|
|
38
|
-
let modified = source;
|
|
39
|
-
const firstImportIndex = source.indexOf("import ");
|
|
40
|
-
if (firstImportIndex !== -1) {
|
|
41
|
-
modified = source.slice(0, firstImportIndex) + importStatement + source.slice(firstImportIndex);
|
|
42
|
-
} else {
|
|
43
|
-
modified = importStatement + source;
|
|
44
|
-
}
|
|
45
|
-
modified = modified.replace(
|
|
46
|
-
/(\{children\})/,
|
|
47
|
-
`<CodeCanvas />
|
|
48
|
-
$1`
|
|
49
|
-
);
|
|
50
|
-
callback(null, modified);
|
|
51
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import "./chunk-Y6FXYEAI.mjs";
|
|
2
|
-
|
|
3
|
-
// src/codecanvas-mount-loader.ts
|
|
4
|
-
function codecanvasMountLoader(source) {
|
|
5
|
-
const callback = this.async();
|
|
6
|
-
if (!source.includes("<html")) {
|
|
7
|
-
callback(null, source);
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
if (source.includes("CodeCanvas")) {
|
|
11
|
-
callback(null, source);
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
const importStatement = `import { CodeCanvas } from "@designtools/next-plugin/codecanvas";
|
|
15
|
-
`;
|
|
16
|
-
let modified = source;
|
|
17
|
-
const firstImportIndex = source.indexOf("import ");
|
|
18
|
-
if (firstImportIndex !== -1) {
|
|
19
|
-
modified = source.slice(0, firstImportIndex) + importStatement + source.slice(firstImportIndex);
|
|
20
|
-
} else {
|
|
21
|
-
modified = importStatement + source;
|
|
22
|
-
}
|
|
23
|
-
modified = modified.replace(
|
|
24
|
-
/(\{children\})/,
|
|
25
|
-
`<CodeCanvas />
|
|
26
|
-
$1`
|
|
27
|
-
);
|
|
28
|
-
callback(null, modified);
|
|
29
|
-
}
|
|
30
|
-
export {
|
|
31
|
-
codecanvasMountLoader as default
|
|
32
|
-
};
|
package/dist/codecanvas.d.mts
DELETED
package/dist/codecanvas.d.ts
DELETED