From 4d61c002fa00a885cacf24705d52dbb88a3db961 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Tue, 2 Jun 2026 14:55:14 +0300 Subject: [PATCH] feat(dom-css): add DOM/CSS package skeleton --- AGENTS.md | 1 + CHANGELOG.md | 1 + bun.lock | 14 ++ package.json | 11 +- packages/dom-css/README.md | 17 ++ packages/dom-css/package.json | 48 +++++ packages/dom-css/src/index.ts | 12 ++ packages/dom-css/src/runtime/browser.ts | 201 ++++++++++++++++++++ packages/dom-css/src/runtime/headless.ts | 23 +++ packages/dom-css/src/runtime/index.ts | 10 + packages/dom-css/src/serialize.ts | 54 ++++++ packages/dom-css/src/types.ts | 49 +++++ packages/dom-css/tsconfig.json | 24 +++ packages/dom-css/tsdown.config.ts | 21 ++ tests/engine/dom-css/runtime.test.ts | 43 +++++ tools/package-quality/src/check-metadata.ts | 8 +- tools/package-quality/src/smoke.ts | 9 +- tsconfig.json | 4 +- vite/aliases.ts | 2 + 19 files changed, 544 insertions(+), 8 deletions(-) create mode 100644 packages/dom-css/README.md create mode 100644 packages/dom-css/package.json create mode 100644 packages/dom-css/src/index.ts create mode 100644 packages/dom-css/src/runtime/browser.ts create mode 100644 packages/dom-css/src/runtime/headless.ts create mode 100644 packages/dom-css/src/runtime/index.ts create mode 100644 packages/dom-css/src/serialize.ts create mode 100644 packages/dom-css/src/types.ts create mode 100644 packages/dom-css/tsconfig.json create mode 100644 packages/dom-css/tsdown.config.ts create mode 100644 tests/engine/dom-css/runtime.test.ts diff --git a/AGENTS.md b/AGENTS.md index a35c4822b..eadee060a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,7 @@ Bun workspace with three packages: - `packages/cli` — `@open-pencil/cli`: headless CLI for .fig inspection, export, linting. Uses `citty` + `agentfmt`. - `packages/docs` — `@open-pencil/docs`: VitePress documentation site. Run with `cd packages/docs && bun run dev`. - `packages/mcp` — `@open-pencil/mcp`: MCP server for AI coding tools. Stdio + HTTP (Hono). Reuses `createServer()` factory with all core tools. +- `packages/dom-css` — `@open-pencil/dom-css`: DOM/CSS projection layer for HTML/CSS/JSX/Tailwind compatibility. Owns DesignDOM types and browser/headless CSS runtime adapters; depends on core scene-graph types but keeps DOM/CSS parser dependencies out of core. - `packages/vue` — `@open-pencil/vue`: headless Vue 3 SDK (Reka UI-style) for building custom OpenPencil-powered editor shells and embedded editing surfaces. Renderless components and composables. The app is one consumer of the SDK. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b14946ca..b83924b68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add design JSX variable helpers so color props can use `designVar()` / `defineVars()` references and emit graph variable bindings. - Add structured design JSX paint helpers for solid fills, multiple fills, and gradients. - Add structured design JSX effect helpers for shadows and blur effects. +- Add the `@open-pencil/dom-css` package skeleton for DOM/CSS projection and browser/headless CSS runtime adapters. - Add type-validated `bindVariable`/`unbindVariable` with event emission and indexed binding format (`fills/N/color` instead of `fills[N]`). - Add `unbind_variable` MCP tool for removing variable bindings. - Add `openpencil analyze overlaps`, the `analyze_overlaps` RPC command, and the `analyze_overlaps` ToolDef for heuristic overlap detection. The command reports sibling overlaps, children overflowing non-clipping parents, and overlay/backdrop patterns, with filters for page/page ID, scope, category, severity, min area/ratio, node type, hidden/locked/absolute nodes, result limit, and `--json` output. diff --git a/bun.lock b/bun.lock index 522602c26..7696e092e 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,7 @@ "@chenglou/pretext": "^0.0.7", "@open-pencil/cli": "workspace:*", "@open-pencil/core": "workspace:*", + "@open-pencil/dom-css": "workspace:*", "@open-pencil/vue": "workspace:*", "@openrouter/ai-sdk-provider": "^2.9.0", "@tailwindcss/vite": "^4.2.1", @@ -155,6 +156,17 @@ "vitepress-plugin-llms": "1.12.2", }, }, + "packages/dom-css": { + "name": "@open-pencil/dom-css", + "version": "0.13.2", + "devDependencies": { + "tsdown": "^0.21.7", + "typescript": "~5.8.3", + }, + "peerDependencies": { + "@open-pencil/core": "workspace:*", + }, + }, "packages/mcp": { "name": "@open-pencil/mcp", "version": "0.13.2", @@ -760,6 +772,8 @@ "@open-pencil/docs": ["@open-pencil/docs@workspace:packages/docs"], + "@open-pencil/dom-css": ["@open-pencil/dom-css@workspace:packages/dom-css"], + "@open-pencil/mcp": ["@open-pencil/mcp@workspace:packages/mcp"], "@open-pencil/vue": ["@open-pencil/vue@workspace:packages/vue"], diff --git a/package.json b/package.json index 796b6608a..e851f5f45 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "build": "bun run build:packages && bun run lint && vite build", "preview": "vite preview", "tauri": "tauri", - "lint": "bun run lint:structure && oxlint -c oxlint.json --type-aware --type-check src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/", - "lint:structure": "oxlint -c oxlint.json vite.config.ts vite/ src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/ tests/ scripts/ tools/", - "format": "oxfmt --write .oxfmtrc.json vite.config.ts vite/ src/ packages/core/src/ packages/cli/src/ packages/mcp/src/ packages/vue/src/ tests scripts/ tools/", + "lint": "bun run lint:structure && oxlint -c oxlint.json --type-aware --type-check src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/ packages/dom-css/src/", + "lint:structure": "oxlint -c oxlint.json vite.config.ts vite/ src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/ packages/dom-css/src/ tests/ scripts/ tools/", + "format": "oxfmt --write .oxfmtrc.json vite.config.ts vite/ src/ packages/core/src/ packages/cli/src/ packages/mcp/src/ packages/vue/src/ packages/dom-css/src/ tests scripts/ tools/", "format:check": "bun run format && status=$(git status --porcelain -uall) && test -z \"$status\" || (echo \"$status\" && exit 1)", "check": "bun run build:packages && bun run lint && tsgo --noEmit && bun run check:vue && bun run check:i18n && bun run check:packages && bun run check:arch && bun run test:type-shapes && bun run test:tools && bun run test:dupes", "check:i18n": "bun tools/i18n/src/check-locales.ts", @@ -29,9 +29,9 @@ "test:coverage": "bun test --coverage ./tests/engine", "test:type-shapes": "bun tools/type-shapes/src/index.ts", "test:tools": "bun tools/test.ts", - "test:dupes": "jscpd packages/core/src packages/cli/src src --min-lines 5 --min-tokens 50 --format typescript --threshold 0", + "test:dupes": "jscpd packages/core/src packages/cli/src packages/dom-css/src src --min-lines 5 --min-tokens 50 --format typescript --threshold 0", "test:packages": "bun tools/package-quality/src/check-metadata.ts && bun tools/package-quality/src/smoke.ts", - "build:packages": "bun --filter @open-pencil/core build && bun --filter @open-pencil/vue build && bun --filter @open-pencil/mcp build && bun --filter @open-pencil/cli build", + "build:packages": "bun --filter @open-pencil/core build && bun --filter @open-pencil/dom-css build && bun --filter @open-pencil/vue build && bun --filter @open-pencil/mcp build && bun --filter @open-pencil/cli build", "open-pencil": "bun packages/cli/src/index.ts", "docs:dev": "bun --filter @open-pencil/docs dev", "docs:build": "bun --filter @open-pencil/docs build", @@ -51,6 +51,7 @@ "@chenglou/pretext": "^0.0.7", "@open-pencil/cli": "workspace:*", "@open-pencil/core": "workspace:*", + "@open-pencil/dom-css": "workspace:*", "@open-pencil/vue": "workspace:*", "@openrouter/ai-sdk-provider": "^2.9.0", "@tailwindcss/vite": "^4.2.1", diff --git a/packages/dom-css/README.md b/packages/dom-css/README.md new file mode 100644 index 000000000..ef3d3988b --- /dev/null +++ b/packages/dom-css/README.md @@ -0,0 +1,17 @@ +# @open-pencil/dom-css + +DOM and CSS projection utilities for OpenPencil. + +This package is the compatibility layer between OpenPencil's scene graph and DOM-shaped design documents. It is intentionally separate from `@open-pencil/core` so browser/CSS parser integrations can evolve without adding DOM dependencies to the renderer and editor core. + +Current scope: + +- DOM-shaped `DesignDocument` / `DesignElement` types +- Browser-backed runtime adapter for native HTML parsing, serialization, and computed-style extraction +- Headless runtime placeholder for future `parse5` / CSSOM-backed execution + +Planned scope: + +- SceneGraph ⇄ DesignDOM conversion +- CSSOM and cascade support in headless contexts +- Tailwind-generated CSS ingestion diff --git a/packages/dom-css/package.json b/packages/dom-css/package.json new file mode 100644 index 000000000..8f2567b0d --- /dev/null +++ b/packages/dom-css/package.json @@ -0,0 +1,48 @@ +{ + "name": "@open-pencil/dom-css", + "version": "0.13.2", + "license": "MIT", + "type": "module", + "imports": { + "#dom-css/*": { + "types": "./src/*", + "bun": "./src/*", + "default": "./dist/*" + } + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "bun": "./src/index.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "sideEffects": false, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "bunx tsdown --config tsdown.config.ts", + "prepack": "bun run build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/open-pencil/open-pencil.git", + "directory": "packages/dom-css" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "peerDependencies": { + "@open-pencil/core": "workspace:*" + }, + "devDependencies": { + "tsdown": "^0.21.7", + "typescript": "~5.8.3" + } +} diff --git a/packages/dom-css/src/index.ts b/packages/dom-css/src/index.ts new file mode 100644 index 000000000..91b90addb --- /dev/null +++ b/packages/dom-css/src/index.ts @@ -0,0 +1,12 @@ +export { serializeHTML, serializeNode } from './serialize' +export { createBrowserCssRuntime, createCssRuntime, createHeadlessCssRuntime } from './runtime' +export type { + CssComputeOptions, + CssRuntime, + DesignDocument, + DesignElement, + DesignNode, + DesignStyleDeclaration, + DesignStyleSheet, + DesignText +} from './types' diff --git a/packages/dom-css/src/runtime/browser.ts b/packages/dom-css/src/runtime/browser.ts new file mode 100644 index 000000000..a79ae9ece --- /dev/null +++ b/packages/dom-css/src/runtime/browser.ts @@ -0,0 +1,201 @@ +import { serializeHTML } from '../serialize' +import type { + CssComputeOptions, + CssRuntime, + DesignDocument, + DesignElement, + DesignNode +} from '../types' + +const DEFAULT_COMPUTED_PROPERTIES = [ + 'align-items', + 'background-color', + 'background-image', + 'border-bottom-color', + 'border-bottom-left-radius', + 'border-bottom-right-radius', + 'border-bottom-width', + 'border-left-color', + 'border-left-width', + 'border-radius', + 'border-right-color', + 'border-right-width', + 'border-top-color', + 'border-top-left-radius', + 'border-top-right-radius', + 'border-top-width', + 'box-shadow', + 'color', + 'display', + 'flex-direction', + 'font-family', + 'font-size', + 'font-style', + 'font-weight', + 'gap', + 'height', + 'justify-content', + 'letter-spacing', + 'line-height', + 'opacity', + 'padding-bottom', + 'padding-left', + 'padding-right', + 'padding-top', + 'text-align', + 'text-decoration-line', + 'width' +] as const + +function requireBrowserDocument(): Document { + if (typeof document === 'undefined') { + throw new TypeError('Browser CSS runtime requires a DOM document') + } + return document +} + +function attributesToRecord(element: Element): Record { + const attrs: Record = {} + for (const attr of Array.from(element.attributes)) { + attrs[attr.name] = attr.value + } + return attrs +} + +function styleToRecord(style: CSSStyleDeclaration): Record | undefined { + const entries: Record = {} + for (const property of Array.from(style)) { + const value = style.getPropertyValue(property) + if (value) entries[property] = value + } + return Object.keys(entries).length > 0 ? entries : undefined +} + +function domNodeToDesignNode(node: Node): DesignNode | null { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent ?? '' + return text.length > 0 ? { type: 'text', text } : null + } + + if (node.nodeType !== Node.ELEMENT_NODE || !(node instanceof Element)) return null + + const children = Array.from(node.childNodes) + .map(domNodeToDesignNode) + .filter((child): child is DesignNode => child !== null) + + return { + type: 'element', + tagName: node.tagName.toLowerCase(), + attrs: attributesToRecord(node), + children, + inlineStyle: node instanceof HTMLElement ? styleToRecord(node.style) : undefined + } +} + +function parseHTML(html: string): DesignDocument { + requireBrowserDocument() + const parsed = new DOMParser().parseFromString(html, 'text/html') + return { + type: 'document', + children: Array.from(parsed.body.childNodes) + .map(domNodeToDesignNode) + .filter((node): node is DesignNode => node !== null) + } +} + +function collectElementPairs( + designNode: DesignNode, + domNode: Node, + pairs: [DesignElement, Element][] +): void { + if (designNode.type === 'text') return + if (!(domNode instanceof Element)) return + + pairs.push([designNode, domNode]) + + const elementChildren = designNode.children.filter( + (child): child is DesignElement => child.type === 'element' + ) + const domChildren = Array.from(domNode.children) + for (const [index, child] of elementChildren.entries()) { + const domChild = domChildren.at(index) + if (domChild) collectElementPairs(child, domChild, pairs) + } +} + +function computedStyleToRecord( + style: CSSStyleDeclaration, + options: CssComputeOptions +): Record { + const entries: Record = {} + const properties = options.includeBrowserDefaults + ? Array.from(style) + : DEFAULT_COMPUTED_PROPERTIES + + for (const property of properties) { + const value = style.getPropertyValue(property) + if (value) entries[property] = value + } + + return entries +} + +async function computeStyles( + designDocument: DesignDocument, + cssText = '', + options: CssComputeOptions = {} +): Promise { + const browserDocument = requireBrowserDocument() + const host = browserDocument.createElement('div') + host.style.cssText = [ + 'position: fixed', + 'left: -100000px', + 'top: 0', + 'width: 1000px', + 'height: auto', + 'visibility: hidden', + 'pointer-events: none', + 'contain: layout style paint' + ].join(';') + + const shadow = host.attachShadow({ mode: 'open' }) + const style = browserDocument.createElement('style') + style.textContent = cssText + shadow.append(style) + + const content = browserDocument.createElement('div') + content.innerHTML = serializeHTML(designDocument) + shadow.append(content) + browserDocument.body.append(host) + + try { + await new Promise((resolve) => { + requestAnimationFrame(() => resolve()) + }) + + const nextDocument = structuredClone(designDocument) + const pairs: [DesignElement, Element][] = [] + const domChildren = Array.from(content.childNodes) + for (const [index, child] of nextDocument.children.entries()) { + const domChild = domChildren.at(index) + if (domChild) collectElementPairs(child, domChild, pairs) + } + + for (const [designElement, domElement] of pairs) { + designElement.computedStyle = computedStyleToRecord(getComputedStyle(domElement), options) + } + + return nextDocument + } finally { + host.remove() + } +} + +export function createBrowserCssRuntime(): CssRuntime { + return { + kind: 'browser', + parseHTML, + serializeHTML, + computeStyles + } +} diff --git a/packages/dom-css/src/runtime/headless.ts b/packages/dom-css/src/runtime/headless.ts new file mode 100644 index 000000000..21126d890 --- /dev/null +++ b/packages/dom-css/src/runtime/headless.ts @@ -0,0 +1,23 @@ +import { serializeHTML } from '../serialize' +import type { CssRuntime, DesignDocument } from '../types' + +function unavailable(feature: string): never { + throw new Error( + `${feature} requires a headless DOM/CSS adapter. Planned dependencies: parse5 + CSSOM.` + ) +} + +export function createHeadlessCssRuntime(): CssRuntime { + return { + kind: 'headless', + parseHTML() { + return unavailable('parseHTML') + }, + serializeHTML(document: DesignDocument) { + return serializeHTML(document) + }, + computeStyles() { + return unavailable('computeStyles') + } + } +} diff --git a/packages/dom-css/src/runtime/index.ts b/packages/dom-css/src/runtime/index.ts new file mode 100644 index 000000000..f46d37e64 --- /dev/null +++ b/packages/dom-css/src/runtime/index.ts @@ -0,0 +1,10 @@ +import type { CssRuntime } from '../types' +import { createBrowserCssRuntime } from './browser' +import { createHeadlessCssRuntime } from './headless' + +export { createBrowserCssRuntime } from './browser' +export { createHeadlessCssRuntime } from './headless' + +export function createCssRuntime(): CssRuntime { + return typeof document !== 'undefined' ? createBrowserCssRuntime() : createHeadlessCssRuntime() +} diff --git a/packages/dom-css/src/serialize.ts b/packages/dom-css/src/serialize.ts new file mode 100644 index 000000000..f9dea2755 --- /dev/null +++ b/packages/dom-css/src/serialize.ts @@ -0,0 +1,54 @@ +import type { DesignDocument, DesignElement, DesignNode, DesignText } from './types' + +const VOID_ELEMENTS = new Set([ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr' +]) + +function escapeText(value: string): string { + return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') +} + +function escapeAttr(value: string): string { + return escapeText(value).replaceAll('"', '"') +} + +function serializeText(node: DesignText): string { + return escapeText(node.text) +} + +function serializeAttrs(node: DesignElement): string { + const attrs = Object.entries(node.attrs) + .filter(([, value]) => value !== '') + .map(([name, value]) => `${name}="${escapeAttr(value)}"`) + + if (attrs.length === 0) return '' + return ` ${attrs.join(' ')}` +} + +function serializeElement(node: DesignElement): string { + const tagName = node.tagName.toLowerCase() + const attrs = serializeAttrs(node) + if (VOID_ELEMENTS.has(tagName)) return `<${tagName}${attrs}>` + return `<${tagName}${attrs}>${node.children.map(serializeNode).join('')}` +} + +export function serializeNode(node: DesignNode): string { + return node.type === 'text' ? serializeText(node) : serializeElement(node) +} + +export function serializeHTML(document: DesignDocument): string { + return document.children.map(serializeNode).join('') +} diff --git a/packages/dom-css/src/types.ts b/packages/dom-css/src/types.ts new file mode 100644 index 000000000..b90cca4a3 --- /dev/null +++ b/packages/dom-css/src/types.ts @@ -0,0 +1,49 @@ +import type { SceneGraph, SceneNode } from '@open-pencil/core/scene-graph' + +export type DesignNode = DesignElement | DesignText + +export interface DesignDocument { + type: 'document' + children: DesignNode[] + stylesheets?: DesignStyleSheet[] + sourceGraph?: SceneGraph +} + +export interface DesignElement { + type: 'element' + tagName: string + attrs: Record + children: DesignNode[] + inlineStyle?: DesignStyleDeclaration + computedStyle?: DesignStyleDeclaration + sourceSceneNodeId?: string + sourceSceneNode?: SceneNode +} + +export interface DesignText { + type: 'text' + text: string +} + +export interface DesignStyleSheet { + type: 'stylesheet' + cssText: string + href?: string +} + +export type DesignStyleDeclaration = Record + +export interface CssComputeOptions { + includeBrowserDefaults?: boolean +} + +export interface CssRuntime { + readonly kind: 'browser' | 'headless' + parseHTML(html: string): DesignDocument + serializeHTML(document: DesignDocument): string + computeStyles( + document: DesignDocument, + cssText?: string, + options?: CssComputeOptions + ): Promise +} diff --git a/packages/dom-css/tsconfig.json b/packages/dom-css/tsconfig.json new file mode 100644 index 000000000..2be6a3b13 --- /dev/null +++ b/packages/dom-css/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "declaration": true, + "declarationDir": "./dist", + "outDir": "./dist", + "rootDir": ".", + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "paths": { + "#dom-css/*": ["./src/*"], + "#core/*": ["../core/src/*"], + "@open-pencil/core": ["../core/src/index.ts"], + "@open-pencil/core/*": ["../core/src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/dom-css/tsdown.config.ts b/packages/dom-css/tsdown.config.ts new file mode 100644 index 000000000..ed597968c --- /dev/null +++ b/packages/dom-css/tsdown.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: { + index: './src/index.ts' + }, + platform: 'neutral', + format: ['esm'], + dts: true, + sourcemap: true, + hash: false, + clean: true, + outDir: './dist', + treeshake: { + moduleSideEffects: false + }, + deps: { + neverBundle: ['@open-pencil/core', /^@open-pencil\/core\//], + onlyBundle: false + } +}) diff --git a/tests/engine/dom-css/runtime.test.ts b/tests/engine/dom-css/runtime.test.ts new file mode 100644 index 000000000..311053cbe --- /dev/null +++ b/tests/engine/dom-css/runtime.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'bun:test' + +import { + createCssRuntime, + createHeadlessCssRuntime, + serializeHTML, + type DesignDocument +} from '@open-pencil/dom-css' + +const documentFixture: DesignDocument = { + type: 'document', + children: [ + { + type: 'element', + tagName: 'div', + attrs: { class: 'card', 'data-id': 'node-1' }, + children: [{ type: 'text', text: 'OpenPencil' }] + } + ] +} + +describe('@open-pencil/dom-css', () => { + it('serializes DesignDOM as HTML', () => { + expect(serializeHTML(documentFixture)).toBe( + '
OpenPencil
' + ) + }) + + it('uses the headless runtime outside browser contexts', () => { + const runtime = createCssRuntime() + + expect(runtime.kind).toBe('headless') + expect(runtime.serializeHTML(documentFixture)).toBe( + '
OpenPencil
' + ) + }) + + it('keeps headless HTML parsing explicit until the parser adapter is added', () => { + const runtime = createHeadlessCssRuntime() + + expect(() => runtime.parseHTML('
')).toThrow('headless DOM/CSS adapter') + }) +}) diff --git a/tools/package-quality/src/check-metadata.ts b/tools/package-quality/src/check-metadata.ts index b55928a6e..b32180941 100644 --- a/tools/package-quality/src/check-metadata.ts +++ b/tools/package-quality/src/check-metadata.ts @@ -1,7 +1,13 @@ import { readFileSync } from 'node:fs' import { join } from 'node:path' -const publicPackages = ['packages/core', 'packages/cli', 'packages/mcp', 'packages/vue'] +const publicPackages = [ + 'packages/core', + 'packages/dom-css', + 'packages/cli', + 'packages/mcp', + 'packages/vue' +] interface PackageJson { name: string diff --git a/tools/package-quality/src/smoke.ts b/tools/package-quality/src/smoke.ts index 216e46943..be0072eb3 100644 --- a/tools/package-quality/src/smoke.ts +++ b/tools/package-quality/src/smoke.ts @@ -4,7 +4,13 @@ import { basename, join } from 'node:path' import { fileURLToPath } from 'node:url' const rootDir = fileURLToPath(new URL('../../..', import.meta.url)) -const packageDirs = ['packages/core', 'packages/vue', 'packages/mcp', 'packages/cli'] +const packageDirs = [ + 'packages/core', + 'packages/dom-css', + 'packages/vue', + 'packages/mcp', + 'packages/cli' +] function run(command: string[], cwd = rootDir): string { const proc = Bun.spawnSync(command, { cwd, stdout: 'pipe', stderr: 'pipe' }) @@ -57,6 +63,7 @@ try { nodeEval("await import('@open-pencil/core')", tempDir) nodeEval("await import('@open-pencil/core/scene-graph')", tempDir) + nodeEval("await import('@open-pencil/dom-css')", tempDir) nodeEval("await import('@open-pencil/vue')", tempDir) nodeEval("await import('@open-pencil/mcp')", tempDir) diff --git a/tsconfig.json b/tsconfig.json index ff64a00d0..f76c0dd0b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,11 +22,13 @@ "#core/*": ["./packages/core/src/*"], "#cli/*": ["./packages/cli/src/*"], "#mcp/*": ["./packages/mcp/src/*"], + "#dom-css/*": ["./packages/dom-css/src/*"], "#tests/*": ["./tests/*"], "tinykeys": ["./node_modules/tinykeys/dist/tinykeys.d.ts"], "@open-pencil/vue": ["./packages/vue/src/index.ts"], "@open-pencil/core": ["./packages/core/src/index.ts"], - "@open-pencil/core/*": ["./packages/core/src/*"] + "@open-pencil/core/*": ["./packages/core/src/*"], + "@open-pencil/dom-css": ["./packages/dom-css/src/index.ts"] } }, "include": ["src/**/*.ts", "src/**/*.vue"], diff --git a/vite/aliases.ts b/vite/aliases.ts index b15fbf664..4e64c984e 100644 --- a/vite/aliases.ts +++ b/vite/aliases.ts @@ -9,8 +9,10 @@ export function createOpenPencilAliases(rootDir: string) { '@': resolve(rootDir, 'src'), '#vue': resolve(rootDir, 'packages/vue/src'), '#core': resolve(rootDir, 'packages/core/src'), + '#dom-css': resolve(rootDir, 'packages/dom-css/src'), '@open-pencil/vue': resolve(rootDir, 'packages/vue/src'), '@open-pencil/core': resolve(rootDir, 'packages/core/src'), + '@open-pencil/dom-css': resolve(rootDir, 'packages/dom-css/src'), 'opentype.js': resolve(rootDir, 'node_modules/opentype.js/dist/opentype.module.js'), mermaid: resolve(rootDir, 'src/app/shell/markdown/index.ts'), 'beautiful-mermaid': resolve(rootDir, 'src/app/shell/markdown/index.ts')