Skip to content

Commit 1a63f66

Browse files
authored
Add a script to develop against a local lexical project (basecamp#1076)
Uses `yalc` to link a local Lexical directory into Lexxy. Runs "pnpm install" and "pnpm build" in the Lexical repo, publishes each Lexical package that Lexxy depends on, then yalc-adds them here. An "unlink" command returns the project to registry versions, and "status" will report on whether the project is linked.
2 parents 4f5c97e + fda37d3 commit 1a63f66

2 files changed

Lines changed: 195 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ A modern rich text editor for Rails.
2121

2222
- Bug reports and pull requests are welcome on [GitHub Issues](https://github.com/basecamp/lexxy/issues). Help is especially welcome with [those tagged as "Help Wanted"](https://github.com/basecamp/lexxy/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).
2323
- For questions and general Lexxy discussion, please use the [Discussions section](https://github.com/basecamp/lexxy/discussions)
24+
- To build with a development version of Lexical, use `bin/link-to-local-lexical`.
2425

2526
## License
2627

bin/link-to-local-lexical

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Develop Lexxy against a local checkout of the lexical monorepo
4+
# (https://github.com/facebook/lexical) instead of the published npm packages.
5+
#
6+
# Uses yalc (https://github.com/wclr/yalc), which simulates publishing by
7+
# copying built packages into a local store and wiring them in as
8+
# "file:.yalc/<name>" dependencies.
9+
#
10+
# Usage: bin/link-to-local-lexical link <path-to-lexical-monorepo>
11+
# bin/link-to-local-lexical unlink
12+
# bin/link-to-local-lexical status
13+
#
14+
# Linking (bin/link-to-local-lexical link <path-to-lexical-monorepo>):
15+
# 1. Runs "pnpm install" and "pnpm prepare-release" in the lexical repo,
16+
# which builds every package into its packages/<name>/npm directory.
17+
# 2. Publishes each of those packages to the local yalc store. All monorepo
18+
# packages are published, not just Lexxy's direct dependencies, so that
19+
# inter-package dependencies (e.g. @lexical/list depending on
20+
# @lexical/utils) also resolve to the local build.
21+
# 3. Runs "yalc add" for the packages here, then writes matching yarn
22+
# "resolutions" entries into package.json. The resolutions force every
23+
# transitive lexical dependency to the local copies; without them, yarn
24+
# would install registry versions alongside the linked ones and the
25+
# editor would break with duplicate-module errors.
26+
# 4. Runs "yarn install" to apply it all.
27+
#
28+
# Unlinking (bin/link-to-local-lexical unlink):
29+
# Reverses the above: collects the linked packages (from package.json and
30+
# from whatever is present in .yalc/), runs "yalc remove", deletes the
31+
# "resolutions" entries, removes the .yalc directory and yalc.lock, and
32+
# runs "yarn install" to restore the registry versions.
33+
#
34+
# Status (bin/link-to-local-lexical status):
35+
# Reports the current state with machine-readable output: prints
36+
# "linked <path-to-lexical-checkout>" and exits 0 when yalc.lock is present
37+
# (the path is recorded in .yalc/.lexical-dir during linking), prints
38+
# "unlinked" and exits 1 otherwise.
39+
40+
set -euo pipefail
41+
42+
LEXXY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
43+
44+
usage() {
45+
cat <<EOF
46+
Usage: bin/link-to-local-lexical link <path-to-lexical-monorepo>
47+
bin/link-to-local-lexical unlink
48+
bin/link-to-local-lexical status
49+
50+
The "link" command links lexxy to a local lexical checkout via yalc. It runs pnpm install +
51+
build in the lexical repo, publishes each lexical package lexxy depends on, then yalc-adds
52+
them here.
53+
54+
The "unlink" command removes the yalc-linked packages and restores the registry versions.
55+
56+
The "status" command prints "linked <path-to-lexical>" (exit 0) or "unlinked" (exit 1).
57+
58+
After source changes in lexical, iterate with:
59+
(cd <lexical-path> && pnpm prepare-release)
60+
(cd <lexical-path>/packages/<name>/npm && npx yalc push)
61+
EOF
62+
}
63+
64+
if [[ $# -eq 0 ]]; then
65+
usage
66+
exit 1
67+
fi
68+
69+
lexxy_direct_lexical_deps() {
70+
node -e "
71+
const pkg = require('$LEXXY_DIR/package.json');
72+
const deps = Object.keys(pkg.dependencies || {});
73+
console.log(deps.filter(d => d === 'lexical' || d.startsWith('@lexical/')).join('\n'));
74+
"
75+
}
76+
77+
all_lexical_packages_in_monorepo() {
78+
local lexical_dir="$1"
79+
node -e "
80+
const fs = require('fs'), path = require('path');
81+
const dir = path.join('$lexical_dir', 'packages');
82+
const names = fs.readdirSync(dir)
83+
.map(d => path.join(dir, d, 'npm', 'package.json'))
84+
.filter(p => fs.existsSync(p))
85+
.map(p => JSON.parse(fs.readFileSync(p)).name)
86+
.filter(n => n === 'lexical' || n.startsWith('@lexical/'));
87+
console.log(names.join('\n'));
88+
"
89+
}
90+
91+
dep_to_package_dir() {
92+
if [[ "$1" == "lexical" ]]; then
93+
echo "lexical"
94+
else
95+
echo "lexical-${1#@lexical/}"
96+
fi
97+
}
98+
99+
write_resolutions() {
100+
local mode="$1" # "add" or "remove"
101+
shift
102+
node -e "
103+
const fs = require('fs');
104+
const path = '$LEXXY_DIR/package.json';
105+
const pkg = JSON.parse(fs.readFileSync(path));
106+
const deps = process.argv.slice(1);
107+
if ('$mode' === 'add') {
108+
pkg.resolutions = pkg.resolutions || {};
109+
for (const d of deps) pkg.resolutions[d] = 'file:.yalc/' + d;
110+
} else {
111+
if (pkg.resolutions) {
112+
for (const d of deps) delete pkg.resolutions[d];
113+
if (Object.keys(pkg.resolutions).length === 0) delete pkg.resolutions;
114+
}
115+
}
116+
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
117+
" -- "$@"
118+
}
119+
120+
if [[ "$1" == "status" && $# -eq 1 ]]; then
121+
if [[ -f "$LEXXY_DIR/yalc.lock" ]]; then
122+
echo "linked $(cat "$LEXXY_DIR/.yalc/.lexical-dir")"
123+
exit 0
124+
else
125+
echo "unlinked"
126+
exit 1
127+
fi
128+
fi
129+
130+
if [[ "$1" == "unlink" && $# -eq 1 ]]; then
131+
cd "$LEXXY_DIR"
132+
mapfile -t packages < <(lexxy_direct_lexical_deps)
133+
if [[ -d .yalc ]]; then
134+
for d in .yalc/@lexical/*; do
135+
[[ -d "$d" ]] && packages+=("@lexical/${d##*/}")
136+
done
137+
[[ -d .yalc/lexical ]] && packages+=("lexical")
138+
fi
139+
if [[ ${#packages[@]} -gt 0 ]]; then
140+
npx yalc remove "${packages[@]}" 2>/dev/null || true
141+
write_resolutions remove "${packages[@]}"
142+
fi
143+
rm -rf .yalc yalc.lock
144+
yarn install
145+
echo "==> Unlinked. Lexxy is back on registry versions."
146+
exit 0
147+
fi
148+
149+
if [[ "$1" != "link" || $# -ne 2 ]]; then
150+
usage
151+
exit 1
152+
fi
153+
154+
LEXICAL_DIR="$(cd "$2" && pwd)"
155+
156+
if [[ ! -f "$LEXICAL_DIR/package.json" ]]; then
157+
echo "Error: $LEXICAL_DIR has no package.json" >&2
158+
exit 1
159+
fi
160+
161+
if [[ "$(node -p "require('$LEXICAL_DIR/package.json').name")" != "@lexical/monorepo" ]]; then
162+
echo "Error: $LEXICAL_DIR is not the lexical monorepo root (@lexical/monorepo)" >&2
163+
exit 1
164+
fi
165+
166+
echo "==> Building lexical at $LEXICAL_DIR"
167+
(cd "$LEXICAL_DIR" && pnpm install && pnpm prepare-release)
168+
169+
mapfile -t packages < <(all_lexical_packages_in_monorepo "$LEXICAL_DIR")
170+
if [[ ${#packages[@]} -eq 0 ]]; then
171+
echo "Error: no @lexical/* packages found under $LEXICAL_DIR/packages" >&2
172+
exit 1
173+
fi
174+
175+
echo "==> Publishing ${#packages[@]} lexical packages via yalc (incl. transitives)"
176+
for dep in "${packages[@]}"; do
177+
pkg_dir="$LEXICAL_DIR/packages/$(dep_to_package_dir "$dep")/npm"
178+
if [[ ! -d "$pkg_dir" ]]; then
179+
echo "Error: expected publish directory not found: $pkg_dir" >&2
180+
echo " (did pnpm prepare-release succeed?)" >&2
181+
exit 1
182+
fi
183+
echo " -> $dep"
184+
(cd "$pkg_dir" && npx yalc publish)
185+
done
186+
187+
echo "==> Adding linked packages to lexxy"
188+
cd "$LEXXY_DIR"
189+
npx yalc add "${packages[@]}"
190+
echo "$LEXICAL_DIR" > .yalc/.lexical-dir
191+
write_resolutions add "${packages[@]}"
192+
yarn install
193+
194+
echo "==> Done. Lexxy is now linked to $LEXICAL_DIR"

0 commit comments

Comments
 (0)