openpencil/packages/kiwi
Danila Poyarkov c2aec9bacb
fix(packages): make packed exports runtime-safe (#665)
* fix(packages): make packed exports runtime-safe

Make checked-in package manifests truthful for ordinary npm and Bun packing, and verify installed artifacts under both runtimes. Centralize package discovery, artifact inspection, and bounded process execution so CI and release publication share the same contracts.

* ci: build package dependencies before checks

Keep workspace jobs independent of ignored dist output now that public package exports consistently resolve built artifacts.

* ci: preserve source-first engine tests

Keep the broader dependency build for package and repository validation, but retain Core-only setup for engine shards so workspace tests continue exercising source modules.

* ci: build engine shard dependencies

Build the seven workspace packages imported by engine tests in dependency order. This preserves a single module instance per package and keeps each clean CI shard independent of ignored dist output.

* ci: restore established package build boundaries

Keep the original repository and engine job setup, and add installed artifact verification only after the existing package build. Avoid changing which module copies unrelated tests execute.

* fix(packages): preserve Bun source identity in tarballs

Retain source-first workspace resolution and ship complete source trees for Bun conditions. Verify clean installed consumers without overlaying archives, reuse consumer validation before release publication, and repair Bun 1.3.10 private source alias resolution.

* refactor(tooling): reuse package and process utilities

Use pkg-types for manifest I/O and types, tinyexec for subprocess lifecycle, and npm's pack listing for release staging. Preserve archive verification and project release invariants rather than reimplementing package-manager file selection.

* refactor(tooling): resolve workspace roots at CLI boundaries

Discover and validate the nearest workspace once, support explicit roots, and pass roots to reusable checks. Replace subprocess entrypoint dispatch with direct calls and preserve aggregate package diagnostics without adding arbitrary test timeout increases.

* fix(release): enforce publication boundaries

Use root version alignment and shared validated npm output parsing. Enforce the public npm registry policy and test verification-before-publication, mismatched artifacts, and partial retries without registry writes.

* refactor(tooling): validate package responses with Valibot

Express npm pack and manifest identity contracts as schemas, infer parsed output types, and preserve contextual failures and relative-path safety. Document Valibot as the first-party validation convention while retaining Zod at SDK boundaries.

* refactor(tooling): validate manifests at input boundaries

Share Valibot schemas for consumed manifest fields, recursive exports, supported workspace declarations, and npm registry responses. Infer domain types and reject malformed metadata instead of silently skipping it downstream.

* refactor(tooling): group package helpers by ownership

Colocate manifest and workspace contracts, separate npm response parsing from generic JSON handling, and split smoke packing, installation, and runtime checks. Remove the release tarball forwarding shim and consolidate its coverage in package-artifacts. Preserve public tooling exports and CLI commands.
2026-09-09 18:25:22 +03:00
..
scripts refactor(kiwi): move FIG GUID helpers 2026-06-30 10:50:39 +03:00
src fix: resolve lint warnings 2026-09-02 01:49:37 +03:00
tests fix(kiwi): interpret compiled schemas without eval 2026-08-29 22:40:49 +03:00
NOTICE fix(kiwi): interpret compiled schemas without eval 2026-08-29 22:40:49 +03:00
package.json fix(packages): make packed exports runtime-safe (#665) 2026-09-09 18:25:22 +03:00
README.md fix(kiwi): interpret compiled schemas without eval 2026-08-29 22:40:49 +03:00
tsconfig.json refactor(kiwi): consume standalone Kiwi package 2026-06-30 10:49:14 +03:00
tsconfig.test.json feat(kiwi): scaffold standalone Kiwi package 2026-06-30 10:48:47 +03:00
tsdown.config.ts chore(tools): harden package and dependency checks 2026-07-01 12:56:45 +03:00

@open-pencil/kiwi

Scene-graph-agnostic Kiwi runtime utilities for OpenPencil.

This package owns pure Kiwi schema parsing, Figma Kiwi schema data, low-level Figma message encode/decode, raw fig-kiwi container helpers, and GUID formatting. Complete .fig archive parsing lives in @open-pencil/fig; SceneGraph integration remains outside this package.

Credits and licensing

The schema runtime is based on Kiwi, a schema-based binary format and JavaScript implementation by Evan Wallace. The original Kiwi implementation is copyright (c) 2016–2023 Evan Wallace and is licensed under the MIT License. OpenPencil's adapted runtime is distributed under the terms of that license; see NOTICE.

bun add @open-pencil/kiwi

Package-local checks

cd packages/kiwi
bun run check

Package scripts:

  • bun run test — package-local Bun tests for schema runtime, Figma schema guards, codec, container, parse, GUID, and variable bindings
  • bun run typecheck — type-checks src, tests, and package scripts
  • bun run build — builds the distributable dist entrypoints
  • bun run smoke:dist — imports built output and exercises the public API
  • bun run check — runs typecheck, tests, build, and dist smoke in sequence

Schema runtime

import { compileSchema, parseSchema, validateSchema } from '@open-pencil/kiwi/schema-runtime'

const schema = parseSchema(`
message Point {
  float x = 1;
  float y = 2;
}
`)

validateSchema(schema)
const codec = compileSchema(schema)
const bytes = codec.encodeMessage({ x: 12, y: 24 })
const point = codec.decodeMessage(bytes)

Figma Kiwi codec

import { createNodeChangesMessage, encodeMessage, initCodec } from '@open-pencil/kiwi/fig/codec'

await initCodec()

const message = createNodeChangesMessage(1, 1, [
  {
    guid: { sessionID: 1, localID: 1 },
    phase: 'CREATED',
    type: 'RECTANGLE',
    name: 'Card',
    size: { x: 320, y: 180 }
  }
])

const bytes = encodeMessage(message)

Boolean operation payloads use Figma's Kiwi enum names. SceneGraph EXCLUDE is a core-level concept and should be serialized as Kiwi XOR before calling the low-level codec.

FIG Kiwi containers

import { buildFigKiwi, parseFigKiwiChunks } from '@open-pencil/kiwi/fig/container'

const container = buildFigKiwi(new Uint8Array([1, 2, 3]))
const chunks = parseFigKiwiChunks(container)

Raw fig-kiwi payload decoding

import { decodeFigKiwiCanvas } from '@open-pencil/kiwi/fig/parse'

const decoded = decodeFigKiwiCanvas(canvasBytes)
console.log(decoded.nodeChanges.length, decoded.blobs.length)

Use parseFigBuffer() from @open-pencil/fig for complete zipped .fig files, including image resources. Use @open-pencil/core/io for conversion into an editable SceneGraph.

GUID helpers

import { guidToString, stringToGuid } from '@open-pencil/kiwi/fig/guid'

const id = guidToString({ sessionID: 1, localID: 42 })
const guid = stringToGuid('1:42')

Public subpaths

  • @open-pencil/kiwi
  • @open-pencil/kiwi/schema-runtime
  • @open-pencil/kiwi/fig
  • @open-pencil/kiwi/fig/codec
  • @open-pencil/kiwi/fig/container
  • @open-pencil/kiwi/fig/guid
  • @open-pencil/kiwi/fig/parse

@open-pencil/kiwi must not import @open-pencil/core, #core/*, app code, Vue code, CLI code, or MCP code.