updated stuff
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@originmain/dev",
|
||||
"version": "0.0.1",
|
||||
"private": false,
|
||||
"description": "Originmain full local-dev SDK. Client runtime (fiber hook) + Node.js server that connects to the Originmain cloud canvas via SSE bridge, enabling design-to-code without Vercel.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./server": "./dist/server.js"
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"scripts": {
|
||||
"build": "node build.mjs",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@originmain/live": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"typescript": "^5.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"keywords": ["originmain", "react", "devtools", "local-dev", "fiber"],
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// ── File writer ───────────────────────────────────────────────────────────────
|
||||
// Applies a design-panel style edit to the component's source file.
|
||||
//
|
||||
// Input: nodeId + CSS property/value (from PATCH_ELEMENT_STYLE)
|
||||
// Output: modified .tsx / .ts / .css file on disk
|
||||
//
|
||||
// applyEditToFile() is called by server.ts for every PATCH_ELEMENT_STYLE
|
||||
// command received from the canvas bridge.
|
||||
//
|
||||
// ── Strategy ──────────────────────────────────────────────────────────────────
|
||||
// Three source styles are detected and rewritten separately:
|
||||
//
|
||||
// 1. Tailwind class string → add/replace utility class
|
||||
// 2. CSS module → update the .module.css file (not yet impl)
|
||||
// 3. Inline style object → update the style={{ }} prop
|
||||
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
export interface ApplyEditOptions {
|
||||
nodeId: string;
|
||||
property: string;
|
||||
value: string;
|
||||
callSite?: { fileName: string; lineNumber: number; columnNumber?: number };
|
||||
}
|
||||
|
||||
export interface ApplyEditResult {
|
||||
written: boolean;
|
||||
filePath?: string;
|
||||
strategy?: 'tailwind' | 'css-module' | 'inline-style';
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function applyEditToFile(opts: ApplyEditOptions): Promise<ApplyEditResult> {
|
||||
const filePath = resolveFilePath(opts);
|
||||
if (!filePath) return { written: false };
|
||||
|
||||
if (!existsSync(filePath)) {
|
||||
return { written: false, error: `Source file not found: ${filePath}` };
|
||||
}
|
||||
|
||||
let source: string;
|
||||
try {
|
||||
source = await readFile(filePath, 'utf-8');
|
||||
} catch (err) {
|
||||
return { written: false, error: `Could not read ${filePath}: ${String(err)}` };
|
||||
}
|
||||
|
||||
const lineNumber = opts.callSite?.lineNumber ?? 1;
|
||||
const strategy = detectStrategy(source, lineNumber);
|
||||
|
||||
let updated: string | null = null;
|
||||
|
||||
switch (strategy) {
|
||||
case 'tailwind':
|
||||
updated = applyTailwindEdit(source, opts.property, opts.value, lineNumber);
|
||||
break;
|
||||
case 'inline-style':
|
||||
updated = applyInlineStyleEdit(source, opts.property, opts.value, lineNumber);
|
||||
break;
|
||||
case 'css-module':
|
||||
return { written: false, strategy: 'css-module' };
|
||||
}
|
||||
|
||||
if (!updated || updated === source) return { written: false, strategy };
|
||||
|
||||
try {
|
||||
await writeFile(filePath, updated, 'utf-8');
|
||||
return { written: true, filePath, strategy };
|
||||
} catch (err) {
|
||||
return { written: false, error: `Could not write ${filePath}: ${String(err)}` };
|
||||
}
|
||||
}
|
||||
|
||||
// ── File path resolution ──────────────────────────────────────────────────────
|
||||
|
||||
function resolveFilePath(opts: ApplyEditOptions): string | null {
|
||||
if (opts.callSite?.fileName) return opts.callSite.fileName;
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Strategy detection ────────────────────────────────────────────────────────
|
||||
|
||||
type Strategy = 'tailwind' | 'inline-style' | 'css-module' | 'unknown';
|
||||
|
||||
function detectStrategy(source: string, lineNumber: number): Strategy {
|
||||
const lines = source.split('\n');
|
||||
const ctx = lines.slice(Math.max(0, lineNumber - 4), lineNumber + 3).join('\n');
|
||||
|
||||
if (/className=["'`]/.test(ctx)) return 'tailwind';
|
||||
if (/style=\{\{/.test(ctx)) return 'inline-style';
|
||||
if (/styles\.[a-zA-Z]|\.module\.(css|scss)/.test(ctx)) return 'css-module';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// ── Tailwind rewrite ──────────────────────────────────────────────────────────
|
||||
|
||||
const CSS_TO_TAILWIND: Record<string, (val: string) => string | null> = {
|
||||
'width': (v) => pxToTailwind('w', v),
|
||||
'height': (v) => pxToTailwind('h', v),
|
||||
'padding': (v) => pxToTailwind('p', v),
|
||||
'padding-top': (v) => pxToTailwind('pt', v),
|
||||
'padding-right': (v) => pxToTailwind('pr', v),
|
||||
'padding-bottom': (v) => pxToTailwind('pb', v),
|
||||
'padding-left': (v) => pxToTailwind('pl', v),
|
||||
'margin': (v) => pxToTailwind('m', v),
|
||||
'margin-top': (v) => pxToTailwind('mt', v),
|
||||
'margin-right': (v) => pxToTailwind('mr', v),
|
||||
'margin-bottom': (v) => pxToTailwind('mb', v),
|
||||
'margin-left': (v) => pxToTailwind('ml', v),
|
||||
'gap': (v) => pxToTailwind('gap', v),
|
||||
'font-size': (v) => pxToTailwind('text', v),
|
||||
'border-radius': (v) => pxToTailwind('rounded', v),
|
||||
'opacity': (v) => `opacity-[${v}]`,
|
||||
'background-color': (v) => `bg-[${v}]`,
|
||||
'color': (v) => `text-[${v}]`,
|
||||
'border-color': (v) => `border-[${v}]`,
|
||||
'display': (v) => displayToTailwind(v),
|
||||
'flex-direction': (v) => ({'row':'flex-row','column':'flex-col','row-reverse':'flex-row-reverse','column-reverse':'flex-col-reverse'}[v] ?? null),
|
||||
'align-items': (v) => ({'flex-start':'items-start','center':'items-center','flex-end':'items-end','stretch':'items-stretch'}[v] ?? null),
|
||||
'justify-content': (v) => ({'flex-start':'justify-start','center':'justify-center','flex-end':'justify-end','space-between':'justify-between'}[v] ?? null),
|
||||
'font-weight': (v) => ({'400':'font-normal','500':'font-medium','600':'font-semibold','700':'font-bold','800':'font-extrabold'}[v] ?? null),
|
||||
'overflow': (v) => ({'hidden':'overflow-hidden','auto':'overflow-auto','scroll':'overflow-scroll','visible':'overflow-visible'}[v] ?? null),
|
||||
};
|
||||
|
||||
function pxToTailwind(prefix: string, val: string): string {
|
||||
const px = parseFloat(val);
|
||||
if (isNaN(px)) return `${prefix}-[${val}]`;
|
||||
const unit = px / 4;
|
||||
const rounded = Math.round(unit * 2) / 2;
|
||||
if (Math.abs(rounded - unit) < 0.15) return `${prefix}-${rounded}`;
|
||||
return `${prefix}-[${val}]`;
|
||||
}
|
||||
|
||||
function displayToTailwind(v: string): string | null {
|
||||
return ({'block':'block','flex':'flex','inline-flex':'inline-flex','inline':'inline','none':'hidden','grid':'grid'})[v] ?? null;
|
||||
}
|
||||
|
||||
const TAILWIND_REMOVE: Record<string, RegExp> = {
|
||||
'width': /\bw-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'height': /\bh-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'padding': /\bp-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'padding-top': /\bpt-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'padding-right': /\bpr-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'padding-bottom': /\bpb-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'padding-left': /\bpl-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'margin': /\bm-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'margin-top': /\bmt-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'margin-right': /\bmr-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'margin-bottom': /\bmb-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'margin-left': /\bml-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'gap': /\bgap-(\[.+?\]|\d+(\.\d+)?)\b/g,
|
||||
'font-size': /\btext-(\[.+?\]|xs|sm|base|lg|xl|2xl|3xl|4xl|5xl)\b/g,
|
||||
'border-radius': /\brounded(-\S+)?\b/g,
|
||||
'opacity': /\bopacity-(\[.+?\]|\d+)\b/g,
|
||||
'background-color': /\bbg-(\[.+?\]|\S+)\b/g,
|
||||
'color': /\btext-(\[.+?\]|\S+)\b/g,
|
||||
'border-color': /\bborder-(\[.+?\]|\S+)\b/g,
|
||||
'display': /\b(block|flex|inline-flex|inline|hidden|grid)\b/g,
|
||||
'flex-direction': /\b(flex-row|flex-col|flex-row-reverse|flex-col-reverse)\b/g,
|
||||
'align-items': /\b(items-start|items-center|items-end|items-stretch)\b/g,
|
||||
'justify-content': /\b(justify-start|justify-center|justify-end|justify-between)\b/g,
|
||||
'font-weight': /\b(font-normal|font-medium|font-semibold|font-bold|font-extrabold)\b/g,
|
||||
'overflow': /\b(overflow-hidden|overflow-auto|overflow-scroll|overflow-visible)\b/g,
|
||||
};
|
||||
|
||||
function applyTailwindEdit(source: string, property: string, value: string, lineNumber: number): string | null {
|
||||
const newClass = CSS_TO_TAILWIND[property]?.(value);
|
||||
if (!newClass) return null;
|
||||
|
||||
const lines = source.split('\n');
|
||||
const classNamePattern = /className=["'`]([^"'`]*)["'`]/;
|
||||
|
||||
let classLineIdx = lineNumber - 1;
|
||||
let found = false;
|
||||
for (let i = classLineIdx; i < Math.min(classLineIdx + 5, lines.length); i++) {
|
||||
if (classNamePattern.test(lines[i] ?? '')) { classLineIdx = i; found = true; break; }
|
||||
}
|
||||
if (!found) return null;
|
||||
|
||||
const line = lines[classLineIdx] ?? '';
|
||||
const match = classNamePattern.exec(line);
|
||||
if (!match) return null;
|
||||
|
||||
const oldClasses = match[1] ?? '';
|
||||
const cleaned = oldClasses.replace(TAILWIND_REMOVE[property] ?? /(?!x)x/, '').trim();
|
||||
const newClasses = cleaned ? `${cleaned} ${newClass}` : newClass;
|
||||
lines[classLineIdx] = line.replace(classNamePattern, `className="${newClasses}"`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ── Inline style rewrite ──────────────────────────────────────────────────────
|
||||
|
||||
function applyInlineStyleEdit(source: string, property: string, value: string, lineNumber: number): string | null {
|
||||
const lines = source.split('\n');
|
||||
const camelProp = property.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
||||
const stylePattern = /style=\{\{([^}]*)\}\}/;
|
||||
|
||||
let styleLineIdx = lineNumber - 1;
|
||||
let found = false;
|
||||
for (let i = styleLineIdx; i < Math.min(styleLineIdx + 5, lines.length); i++) {
|
||||
if (stylePattern.test(lines[i] ?? '')) { styleLineIdx = i; found = true; break; }
|
||||
}
|
||||
if (!found) return null;
|
||||
|
||||
const line = lines[styleLineIdx] ?? '';
|
||||
const match = stylePattern.exec(line);
|
||||
if (!match) return null;
|
||||
|
||||
const styleBody = match[1] ?? '';
|
||||
const propPattern = new RegExp(`${camelProp}:\\s*[^,}]+`);
|
||||
const newEntry = `${camelProp}: ${JSON.stringify(value)}`;
|
||||
|
||||
let newStyleBody: string;
|
||||
if (propPattern.test(styleBody)) {
|
||||
newStyleBody = styleBody.replace(propPattern, newEntry);
|
||||
} else {
|
||||
const trimmed = styleBody.trimEnd();
|
||||
newStyleBody = trimmed + (trimmed.endsWith(',') ? ' ' : ', ') + newEntry;
|
||||
}
|
||||
|
||||
lines[styleLineIdx] = line.replace(stylePattern, `style={{ ${newStyleBody.trim()} }}`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// @originmain/dev — client entry
|
||||
//
|
||||
// Side-effect-only re-export of the live SDK.
|
||||
// Install this before React in your app entry point (or use withOriginmain()).
|
||||
//
|
||||
// Usage:
|
||||
// import '@originmain/dev'; // MUST be before React
|
||||
//
|
||||
// The browser-side fiber hook will activate when the page runs inside an
|
||||
// Originmain artboard iframe (via postMessage) OR when connected to the
|
||||
// cloud canvas via the SDK bridge (via SSE).
|
||||
|
||||
export * from '@originmain/live';
|
||||
import '@originmain/live';
|
||||
@@ -0,0 +1,201 @@
|
||||
// @originmain/dev — server runtime
|
||||
//
|
||||
// Runs inside the Next.js dev server process (Node.js, not the browser).
|
||||
// Connects outbound to the Originmain cloud canvas bridge via two SSE channels:
|
||||
//
|
||||
// 1. POST {bridgeUrl}/api/sdk/{projectId} ← SDK pushes fiber events
|
||||
// 2. GET {bridgeUrl}/api/sdk/{projectId}/commands ← SDK receives canvas commands
|
||||
//
|
||||
// Usage (called by withOriginmain() in @originmain/next when SDK_TOKEN is set):
|
||||
//
|
||||
// import { startDevServer } from '@originmain/dev/server';
|
||||
// startDevServer({
|
||||
// projectId: process.env.ORIGINMAIN_PROJECT_ID,
|
||||
// sdkToken: process.env.ORIGINMAIN_SDK_TOKEN,
|
||||
// bridgeUrl: 'https://originmain.com', // or custom cloud URL
|
||||
// localUrl: 'http://localhost:3000', // the Next.js dev server
|
||||
// });
|
||||
//
|
||||
// The server runtime does NOT instrument React itself — that is handled by
|
||||
// @originmain/live in the browser. Instead, this runtime:
|
||||
//
|
||||
// a. Forwards fiber events it receives from the browser (via localhost:3000
|
||||
// acting as a relay) to the cloud canvas bridge.
|
||||
// b. Receives edit commands from the cloud canvas bridge and applies them
|
||||
// to source files on disk (file-write capability).
|
||||
// c. Optionally applies commands to the browser via the app's own SSE relay.
|
||||
|
||||
import type { HostMessage } from './types.js';
|
||||
import { applyEditToFile } from './file-writer.js';
|
||||
|
||||
export interface DevServerOptions {
|
||||
/** Originmain project ID (from canvas URL). */
|
||||
projectId: string;
|
||||
/** SDK token issued in project settings (POST /api/sdk/token). */
|
||||
sdkToken: string;
|
||||
/** Root URL of the Originmain cloud canvas. Default: https://originmain.com */
|
||||
bridgeUrl?: string;
|
||||
/** URL of the local Next.js dev server. Default: http://localhost:3000 */
|
||||
localUrl?: string;
|
||||
/** Enable verbose logging. Default: false. */
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
let running = false;
|
||||
|
||||
/** Start the SDK dev server. Idempotent — safe to call multiple times. */
|
||||
export function startDevServer(opts: DevServerOptions): void {
|
||||
if (running) return;
|
||||
running = true;
|
||||
|
||||
const {
|
||||
projectId,
|
||||
sdkToken,
|
||||
bridgeUrl = 'https://originmain.com',
|
||||
debug = false,
|
||||
} = opts;
|
||||
|
||||
const log = debug ? (...args: unknown[]) => console.log('[originmain/dev]', ...args) : () => {};
|
||||
const warn = (...args: unknown[]) => console.warn('[originmain/dev]', ...args);
|
||||
|
||||
log(`Connecting to bridge for project ${projectId}…`);
|
||||
|
||||
void connectCommandsStream({ projectId, sdkToken, bridgeUrl, log, warn });
|
||||
}
|
||||
|
||||
// ── Commands stream (Canvas → SDK) ────────────────────────────────────────────
|
||||
// Long-lived SSE connection to GET /api/sdk/{projectId}/commands.
|
||||
// Reconnects automatically on disconnect.
|
||||
|
||||
interface ConnectOpts {
|
||||
projectId: string;
|
||||
sdkToken: string;
|
||||
bridgeUrl: string;
|
||||
log: (...a: unknown[]) => void;
|
||||
warn: (...a: unknown[]) => void;
|
||||
}
|
||||
|
||||
async function connectCommandsStream(opts: ConnectOpts): Promise<void> {
|
||||
const { projectId, sdkToken, bridgeUrl, log, warn } = opts;
|
||||
const url = `${bridgeUrl}/api/sdk/${encodeURIComponent(projectId)}/commands`;
|
||||
const headers = { Authorization: `Bearer ${sdkToken}` };
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
log('Subscribing to commands stream…');
|
||||
const response = await fetch(url, { headers, signal: undefined });
|
||||
|
||||
if (!response.ok) {
|
||||
warn(`Commands stream returned ${response.status} — retrying in 5s`);
|
||||
await sleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
warn('Commands stream has no body — retrying in 5s');
|
||||
await sleep(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
log('Commands stream connected ✓');
|
||||
await readSseStream(response.body, (message) => {
|
||||
void handleCommand(message as HostMessage, opts);
|
||||
});
|
||||
|
||||
// Stream ended (server closed connection) — reconnect after a short delay.
|
||||
log('Commands stream closed — reconnecting in 2s');
|
||||
await sleep(2000);
|
||||
|
||||
} catch (err) {
|
||||
warn('Commands stream error:', err instanceof Error ? err.message : err);
|
||||
await sleep(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── SSE stream reader ─────────────────────────────────────────────────────────
|
||||
|
||||
async function readSseStream(
|
||||
body: ReadableStream<Uint8Array>,
|
||||
onMessage: (data: unknown) => void,
|
||||
): Promise<void> {
|
||||
const decoder = new TextDecoder();
|
||||
const reader = body.getReader();
|
||||
let buffer = '';
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() ?? '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.startsWith('data: ')) continue;
|
||||
const raw = line.slice(6).trim();
|
||||
if (!raw) continue;
|
||||
try {
|
||||
onMessage(JSON.parse(raw));
|
||||
} catch { /* malformed JSON — skip */ }
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Command handler ───────────────────────────────────────────────────────────
|
||||
|
||||
async function handleCommand(msg: HostMessage, opts: ConnectOpts): Promise<void> {
|
||||
const { log, warn } = opts;
|
||||
|
||||
// Bridge status ping — not a real command.
|
||||
if ((msg as Record<string, unknown>).type === '__bridge_status__') return;
|
||||
|
||||
log(`Received command: ${msg.type}`);
|
||||
|
||||
switch (msg.type) {
|
||||
case 'PATCH_ELEMENT_STYLE': {
|
||||
// Apply style change to source file via callSite information.
|
||||
// The browser SDK has already applied the inline style; here we
|
||||
// write it to the source file so the change persists.
|
||||
const { nodeId, property, value } = msg;
|
||||
if (nodeId && property !== undefined && value !== undefined) {
|
||||
const result = await applyEditToFile({ nodeId, property, value });
|
||||
if (result.written) {
|
||||
log(` → wrote ${property}: ${value} to ${result.filePath}`);
|
||||
} else {
|
||||
log(` → no callSite for ${nodeId} — inline style applied, source unchanged`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'NAVIGATE':
|
||||
case 'SET_DESIGN_TOKENS':
|
||||
case 'SELECT_COMPONENT':
|
||||
case 'DESELECT':
|
||||
case 'REQUEST_ELEMENT_STYLES':
|
||||
case 'CAPTURE_THUMBNAIL':
|
||||
case 'CAPTURE_SNAPSHOT':
|
||||
case 'CANCEL_SNAPSHOT':
|
||||
// These commands target the browser-side SDK (already handled via postMessage
|
||||
// when iframed). When using the bridge (non-iframe mode), these would be
|
||||
// forwarded to the browser via the local relay. Not yet implemented.
|
||||
log(` → forwarding ${msg.type} to browser (not yet implemented)`);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn(`Unknown command type: ${(msg as Record<string, unknown>).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Utility ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// ── Shared message types (subset of packages/renderer/src/protocol.ts) ────────
|
||||
// Self-contained copy so @originmain/dev has no workspace dependency on
|
||||
// @originmain/renderer. Must stay in sync with the canonical protocol.
|
||||
|
||||
export type HostMessage =
|
||||
| { type: 'SET_DESIGN_TOKENS'; tokens: Record<string, string> }
|
||||
| { type: 'NAVIGATE'; path: string }
|
||||
| { type: 'SELECT_COMPONENT'; nodeId: string }
|
||||
| { type: 'DESELECT' }
|
||||
| { type: 'REQUEST_ELEMENT_STYLES'; nodeId: string }
|
||||
| { type: 'PATCH_ELEMENT_STYLE'; nodeId: string; property: string; value: string }
|
||||
| { type: 'PATCH_CHILDREN_STYLE'; parentNodeId: string; selector: string; property: string; value: string }
|
||||
| { type: 'REMOVE_ELEMENT'; nodeId: string }
|
||||
| { type: 'CAPTURE_THUMBNAIL' }
|
||||
| { type: 'CAPTURE_SNAPSHOT'; nodeId: string }
|
||||
| { type: 'CANCEL_SNAPSHOT' };
|
||||
|
||||
export interface CallSite {
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
columnNumber?: number;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user