#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
PKG_NAME="@weaver.sh/editor"
PKG_VERSION="0.1.1"
# Targets to build
TARGETS=(bundler web nodejs deno)
COMMAND="${1:-build}"
shift || true
# Feature variants
declare -A VARIANTS=(
["core"]=""
["collab"]="collab"
)
build() {
local target="$1"
local variant="$2"
local features="$3"
local out_dir="pkg/${variant}/${target}"
echo "Building ${variant}/${target}..."
local feature_args="--no-default-features"
if [[ -n "$features" ]]; then
feature_args="$feature_args --features $features"
fi
wasm-pack build \
--out-name weaver_editor \
--out-dir "$out_dir" \
--target "$target" \
$feature_args
# Report size
local wasm_file="${out_dir}/weaver_editor_bg.wasm"
if [[ -f "$wasm_file" ]]; then
local size=$(ls -lh "$wasm_file" | awk '{print $5}')
echo " → ${size}"
fi
}
generate_package_json() {
local variant="$1"
local out_dir="pkg/${variant}"
local pkg_suffix=""
local description=""
if [[ "$variant" == "collab" ]]; then
pkg_suffix="-collab"
description="Weaver markdown editor with collaborative editing (Loro CRDT + iroh P2P)"
else
pkg_suffix="-core"
description="Weaver markdown editor (local editing, lightweight)"
fi
# Worker export only for collab variant
local worker_export=""
local worker_files=""
if [[ "$variant" == "collab" ]]; then
worker_export=',
"./worker": {
"import": "./worker/editor_worker.js"
}'
worker_files=',
"worker/"'
fi
cat > "${out_dir}/package.json" << EOF
{
"name": "${PKG_NAME}${pkg_suffix}",
"version": "${PKG_VERSION}",
"description": "${description}",
"license": "MPL-2.0",
"repository": {
"type": "git",
"url": "https://tangled.org/nonbinary.computer/weaver"
},
"keywords": ["atproto", "markdown", "editor", "wasm", "weaver"],
"main": "index.js",
"module": "index.js",
"types": "index.d.ts",
"exports": {
".": {
"import": "./index.js",
"types": "./index.d.ts"
},
"./types": {
"import": "./types.js",
"types": "./types.d.ts"
},
"./wasm/bundler": {
"import": "./bundler/weaver_editor.js",
"types": "./bundler/weaver_editor.d.ts"
},
"./wasm/web": {
"import": "./web/weaver_editor.js",
"types": "./web/weaver_editor.d.ts"
},
"./wasm/nodejs": {
"import": "./nodejs/weaver_editor.js",
"require": "./nodejs/weaver_editor.js",
"types": "./nodejs/weaver_editor.d.ts"
},
"./wasm/deno": {
"import": "./deno/weaver_editor.js",
"types": "./deno/weaver_editor.d.ts"
},
"./weaver-editor.css": "./weaver-editor.css"${worker_export}
},
"files": [
"index.js",
"index.d.ts",
"types.js",
"types.d.ts",
"weaver-editor.css",
"bundler/",
"web/",
"nodejs/",
"deno/",
"README.md"${worker_files}
]
}
EOF
}
generate_readme() {
local variant="$1"
local out_dir="pkg/${variant}"
cat > "${out_dir}/README.md" << 'EOF'
# @weaver.sh/editor
WASM-based markdown editor for weaver.sh.
## Installation
```bash
npm install @weaver.sh/editor-core # Local editing only
npm install @weaver.sh/editor-collab # With collaborative editing
```
## Usage
### With a bundler (webpack, vite, etc.)
```javascript
import init, { JsEditor } from '@weaver.sh/editor-core';
await init();
const editor = JsEditor.fromMarkdown('# Hello\n\nWorld');
console.log(editor.getMarkdown());
```
### Direct browser usage (no bundler)
```html
```
### Node.js
```javascript
const { JsEditor } = require('@weaver.sh/editor-core/nodejs');
```
## API
See the TypeScript definitions for full API documentation.
### Core
- `JsEditor.new()` - Create empty editor
- `JsEditor.fromMarkdown(content)` - Create from markdown
- `JsEditor.fromSnapshot(entry)` - Create from EntryJson snapshot
- `editor.getMarkdown()` - Get markdown content
- `editor.getSnapshot()` - Get EntryJson for drafts
- `editor.toEntry()` - Get validated EntryJson for publishing
- `editor.executeAction(action)` - Execute an EditorAction
- `editor.setTitle(title)` / `editor.setPath(path)` / `editor.setTags(tags)`
### Images
- `editor.addPendingImage(image)` - Track pending upload
- `editor.finalizeImage(localId, finalized)` - Mark upload complete
- `editor.getPendingImages()` - Get images awaiting upload
- `editor.getStagingUris()` - Get staging record URIs for cleanup
### Collab (editor-collab only)
- `JsCollabEditor` - Collaborative editor with Loro CRDT
- `editor.exportUpdates()` / `editor.importUpdates(bytes)`
- `editor.addPeer(nodeId)` / `editor.removePeer(nodeId)`
EOF
}
build_worker() {
echo "Building editor worker WASM..."
# Build the worker binary from weaver-editor-crdt
# Must be in workspace root for cargo to find the crate
local workspace_root="$(cd ../.. && pwd)"
export RUSTFLAGS='--cfg getrandom_backend="wasm_js"'
(cd "$workspace_root" && cargo build \
-p weaver-editor-crdt \
--bin editor_worker \
--target wasm32-unknown-unknown \
--release \
--features collab)
# Create worker output directory
local worker_out="pkg/collab/worker"
mkdir -p "$worker_out"
# Run wasm-bindgen with no-modules target for web worker compatibility
wasm-bindgen \
"$workspace_root/target/wasm32-unknown-unknown/release/editor_worker.wasm" \
--out-dir "$worker_out" \
--target no-modules \
--no-typescript
# Report size
local wasm_file="${worker_out}/editor_worker_bg.wasm"
if [[ -f "$wasm_file" ]]; then
local size=$(ls -lh "$wasm_file" | awk '{print $5}')
echo " → Worker WASM: ${size}"
fi
}
build_typescript() {
echo "Building TypeScript wrapper..."
# Install deps if needed
if [[ ! -d "ts/node_modules" ]]; then
(cd ts && npm install)
fi
# Link WASM output so TypeScript can find it during compilation
# Use collab/bundler as source - it has all exports (JsCollabEditor + JsEditor)
# Core variant users who import collab will get runtime error, which is expected
rm -rf ts/bundler
ln -s ../pkg/collab/bundler ts/bundler
# Compile TypeScript
(cd ts && npm run build)
# Copy to pkg variants
for variant in "${!VARIANTS[@]}"; do
local out_dir="pkg/${variant}"
# Copy compiled JS/TS
cp -r ts/dist/* "${out_dir}/"
# Copy CSS
cp ts/weaver-editor.css "${out_dir}/"
done
echo " → TypeScript wrapper built"
}
do_build() {
# Clean previous builds
rm -rf pkg
# Build all combinations
for variant in "${!VARIANTS[@]}"; do
features="${VARIANTS[$variant]}"
for target in "${TARGETS[@]}"; do
build "$target" "$variant" "$features"
done
generate_package_json "$variant"
generate_readme "$variant"
# Clean up wasm-pack artifacts we don't need
find "pkg/${variant}" -name ".gitignore" -delete
find "pkg/${variant}" -name "package.json" -path "*/bundler/*" -delete
find "pkg/${variant}" -name "package.json" -path "*/web/*" -delete
find "pkg/${variant}" -name "package.json" -path "*/nodejs/*" -delete
find "pkg/${variant}" -name "package.json" -path "*/deno/*" -delete
done
# Build worker WASM for collab variant
build_worker
# Build TypeScript wrapper
build_typescript
echo ""
echo "Build complete!"
echo ""
echo "Editor WASM:"
ls -lh pkg/core/web/*.wasm pkg/collab/web/*.wasm 2>/dev/null || true
echo ""
echo "Worker WASM (collab only):"
ls -lh pkg/collab/worker/*.wasm 2>/dev/null || true
echo ""
echo "Packages:"
echo " pkg/core/ - @weaver.sh/editor-core (local editing)"
echo " pkg/collab/ - @weaver.sh/editor-collab (with CRDT collab)"
}
do_pack() {
echo "Packing..."
for variant in "${!VARIANTS[@]}"; do
echo " ${variant}..."
(cd "pkg/${variant}" && npm pack)
done
echo ""
echo "Tarballs created:"
ls -lh pkg/*/*.tgz 2>/dev/null || true
}
do_publish() {
local tag="${1:-}"
local tag_arg=""
if [[ -n "$tag" ]]; then
tag_arg="--tag $tag"
fi
echo "Publishing..."
for variant in "${!VARIANTS[@]}"; do
echo " ${variant}..."
(cd "pkg/${variant}" && npm publish --access public $tag_arg)
done
echo ""
echo "Published!"
}
usage() {
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " build Build all variants and targets (default)"
echo " pack Create npm tarballs"
echo " publish Publish to npm registry"
echo " all Build, pack, and publish"
echo ""
echo "Options for publish:"
echo " --tag Publish with a specific tag (e.g., 'next', 'beta')"
}
case "$COMMAND" in
build)
do_build
;;
pack)
do_pack
;;
publish)
do_publish "$@"
;;
all)
do_build
do_pack
do_publish "$@"
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: $COMMAND"
usage
exit 1
;;
esac