Skip to content

Latest commit

 

History

826 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Scripture Editors

This monorepo contains packages for various Scripture editors.

Each Scripture application's editor will have many behaviors in common with other Scripture applications. Each will need some features that are unique. We are developing all of these parts in a compatible way and in one place in order to maximize collaboration and sharing.

In this monorepo:

  • Each application produces their own editor package - the application uses the package produced from this repo because the source is there.
  • Common nodes, plugins, and formatters for the toolbar plugin.
  • Specific sets of nodes, plugins, and formatters for each data type extending from the common items where applicable.

Sharing in this monorepo is a commitment to maintain and organize it. Each application package is free to move in its own direction but keeping in mind items that can be pushed up outside the specific editor package to be used in common.

Where this repository lives

This repository is maintained by the Paratext 10 Studio team at paranext/scripture-editors, and it is the source of truth for the editor packages. Day-to-day development happens here.

It began as a copy of eten-tech-foundation/scripture-editors, carrying that repo's full history, its MIT license, and its attribution. It was seeded with a curated set of refs — main, platform-yalc, release-prep, and the release tags — so if you need a branch that was left behind, see Transferring work from the eten-tech-foundation repository. It is not a GitHub fork: while a repository is a fork, GitHub always defaults new pull requests to the upstream repository and offers no setting to change that, which makes it far too easy to open a PR against the wrong repo. Everything a fork would give us still works through an ordinary git remote — see Working with the eten-tech-foundation repository.

Relationship to paranext-core

Platform.Bible consumes two of this repo's packages — @eten-tech-foundation/platform-editor and @eten-tech-foundation/scripture-utilities — but not from the npm registry. Its preinstall step clones this repo (or finds an existing checkout), builds those two packages, and stages a copy of each into paranext-core/dev-packages/staging/, which its package.json files then reference with file: specifiers.

Because the build is committed, that copy step needs nothing from this repo's toolchain.

The practical consequence for anyone working here: this repo's package.json files are authoritative for its own dependencies. Adding, bumping, or removing a dependency here flows into paranext-core on its next npm install, with nothing to restate on the consuming side. See paranext-core's README for the consuming half, including how to get your changes here into a running Platform.Bible.

Which revision paranext-core builds is pinned in its dev-packages.json, normally the platform-yalc branch.

Developer Quick Start

  1. Install Volta.
  2. Clone the monorepo:
    git clone https://github.com/paranext/scripture-editors.git
    cd scripture-editors
    pnpm install
  3. Install Nx globally (note we intentionally use npm rather than pnpm for global installs, see JavaScript Tool Manager):
    npm i -g nx
  4. Run one of the top level developer environments (see the Nx Graph below), e.g.:
    nx dev perf-react

JavaScript Tool Manager

You can use Volta with this repo to use the right version of tools such as Node.js and PNPM.

If you don't use Volta just look at the volta property in package.json to see the right tool versions to install in your preferred way.

NOTE: there is a known limitation using PNPM with Volta. So set your environment variable VOLTA_FEATURE_PNPM to 1. Also to install packages globally, use NPM instead of PNPM (only for global installs). For an example, see step 2 of Developer Quick Start.

Nx Monorepo Build System

Source Demo App
perf-vanilla nx dev perf-vanilla
perf-react nx dev perf-react
platform nx dev platform
scribe-editor* nx dev scribe-editor

* This item appears in 2 lists as it has both the Demo App and Package in the same source folder.

Source Package
platform-editor Github Tag
scribe-editor Github Tag*
utilities Github Tag

* This package has not yet been published to the new NPM organization. The previous version of scribe-editor is here Github Tag.

Source Library
shared-react internal non-published
shared internal non-published
test-data internal non-published
---
title: Nx Graph
---
graph TB
  V(perf-vanilla) --> T(test-data)
  V --> S(shared)
  S --> T
  S --> U(utilities)
  R(perf-react) --> T
  R --> S
  R --> SR(shared-react)
  SR --> S
  SR --> U
  SB(scribe-editor) --> SR
  SB --> S
  SB --> U
  P(platform) --> PE(platform-editor)
  P --> T
  P --> U
  PE --> SR
  PE --> S
  PE --> U
Loading

This workspace has been generated by Nx, a Smart, fast and extensible build system.

Nx Installed Globally?

If you haven't installed Nx globally (as recommended in step 2 of Developer Quick Start), then just prefix each call to nx with pnpm, e.g. pnpm nx build perf-react.

Running tasks

To execute tasks with Nx use the following syntax:

nx <target> <project> <...options>
# e.g.
nx build perf-react

You can also run multiple targets:

nx run-many -t <target1> <target2>

..or add -p to filter specific projects

nx run-many -t <target1> <target2> -p <proj1> <proj2>

Targets can be defined in the package.json or projects.json. Learn more in the docs.

Generate code

If you happen to use Nx plugins, you can leverage code generators that might come with it.

Run nx list to get a list of available plugins and whether they have generators. Then run nx list <plugin-name> to see what generators are available.

Learn more about Nx generators on the docs.

Want better Editor Integration?

Have a look at the Nx Console extensions. It provides autocomplete support, a UI for exploring and running tasks & generators, and more! Available for VSCode, IntelliJ and comes with a LSP for Vim users.

Ready to deploy?

Just run nx build perf-react to build that application. The build artifacts will be stored in the dist/ directory, ready to be deployed.

Set up CI!

Nx comes with local caching already built-in (check your nx.json). On CI you might want to go a step further.

Connect with us!

Testing

The unit tests run automatically on each GitHub PR (see test.yml).

To run all TS unit tests:

nx run-many -t test

To run all TS unit tests for a single package (in this example the shared package):

nx test shared

To run all TS unit tests watching for file changes:

  • On Windows:
    nx watch --all -- nx test %NX_PROJECT_NAME%
  • On Linux or macOS:
    nx watch --all -- nx test \$NX_PROJECT_NAME

You can also use the recommended VS Code extensions to run tests there. This is particularly useful for running individual tests and debugging.

API Extraction

If you change the public API of a package, run nx extract-api to update its API report:

nx extract-api <package-name>  # e.g., nx extract-api platform-editor

Or update all packages at once:

nx run-many -t extract-api

The generated API report files should be committed alongside your changes.

Formatting, Linting and Typechecking

Formatting happens automatically when you commit — the pre-commit hook runs Prettier over your staged files, and that is the gate. If you use VS Code with this repo's recommended extensions, files will also be formatted when you save. You shouldn't normally need to format anything by hand.

To check TypeScript for readability, maintainability, and functionality errors, run the following from the repo root (or just use VS Code with this repo's recommended extensions).

nx run-many -t lint # to check linting
nx run-many -t typecheck # to check types
npx prettier --write <files> # only if you need to format something by hand

Use Prettier directly rather than nx format:write. CI runs nx format:check purely as a backstop for commits that bypassed the hook; it exists because nx format once resolved a Prettier configuration that didn't quite match invoking Prettier directly. Several Nx majors have passed and that may well be fixed, but it has never been reconfirmed — so prefer the tool the hook uses.

Note .prettierignore excludes some files that still get staged, notably pnpm-lock.yaml and **/tsconfig*.json. Prettier leaves those alone by design; that isn't the hook failing.

Dependency Updates

Dependabot is configured to open security PRs only, so any dependabot PR on this repo is an advisory rather than a routine bump. We supersede those PRs with a single hand-made batch commit, because dependabot resolves pnpm monorepo trees poorly and because a version bump often fails to clear the advisory on its own.

See docs/dependency-updates.md for the procedure, how to write the pnpm-workspace.yaml overrides, and why a green CI run is not evidence that a vulnerability is fixed. Claude Code users can invoke the update-dependencies skill, which follows that doc.

TypeScript Code Intelligence

This repo gives Claude Code a TypeScript language server (jump-to-definition, find-references, and type-error diagnostics across the monorepo). It works on Windows, macOS, and Linux with no manual setup beyond pnpm install.

typescript-language-server is a repo devDependency, so pnpm install provides it (and the typescript it needs is already in the workspace). A committed Claude Code plugin (.claude/skills/typescript-lsp-volta) launches it and loads automatically when you start Claude Code from the repo root — no global install, no /plugin install step.

It uses a custom plugin rather than the official typescript-lsp, which fails under Volta on Windows; see the plugin README for why and how.

Linux / WSL override

The custom plugin works on Windows and macOS but currently has a loading issue on Linux / WSL where Claude Code recognises the plugin yet fails to start its LSP server. On Linux the official plugin works fine (Volta shims are real executables, not .cmd files), so add this override to .claude/settings.local.json (gitignored):

{
  // ... keep any existing keys ...
  "enabledPlugins": {
    "typescript-lsp@claude-plugins-official": true,
    "typescript-lsp-volta@skills-dir": false,
  },
}

Then run /reload-plugins inside Claude Code.

Notes:

  • Start Claude Code from the repo root; project-scope plugins don't load from a subdirectory. After changing directories, run /reload-plugins.
  • The typescript-language-server dev dependency is installed for everyone via pnpm install but is only used by Claude Code. If you don't use Claude Code, you can ignore this feature.

Collaborative Web Development Environment

Thanks to CodeSandbox for the instant dev environment: https://codesandbox.io/p/github/eten-tech-foundation/scripture-editors/main

Plain Vanilla JS and React

Lexical works with plain-vanilla JS/TS as well as with React. To that end, the editor packages in this repo perf-react and perf-vanilla are 2 editor components that behave the same to edit the PERF data format.

If you are using a framework other than React and need to wrap a plain-vanilla JS editor for your framework, you could add your own vanilla TS editor package to this repo. By comparing perf-vanilla and perf-react you can see how to take any existing React plugins you might want and convert them to vanilla TS.

The platform-yalc branch

paranext-core and paratext-10-studio pin the platform-yalc branch rather than main. It exists so that a change here can be coordinated with the change that consumes it: a breaking edit lands on main first, and platform-yalc is moved forward only once the consuming side is ready, so build servers never pick up a half-finished handoff.

To move it forward, use the script rather than doing it by hand:

# with platform-yalc checked out and a clean tree
npm run move-platform-yalc

It resets your local branch to origin's state (this branch is force-pushed, so your copy is stale by design), rebases onto origin/main, runs the consumer-lockfile check below, and force-pushes only if that passes. It refuses to run from another branch, on a dirty tree, or with commits that exist only on your machine. See Dependency changes need a paranext-core lockfile PR for the check itself, its flags, and why to give it a GITHUB_TOKEN locally.

Doing it by hand skips the lockfile check, which is the failure that reaches every paranext-core build rather than only you.

The name is historical — it refers to yalc, which paranext-core no longer uses. The branch's coordination role is still real, so it stays.

Dependency changes need a paranext-core lockfile PR

paranext-core's package-lock.json records these packages' dependency closure. If a dependency was added, removed, or its version range changed here since platform-yalc last moved, every paranext-core build breaks when it moves again — its npm ci refuses to run until its lockfile is refreshed.

This is about the dependencies these packages declare, not about their own version numbers. Raising platform-editor from 0.8.16 to 0.8.17 needs nothing on core's side; changing what platform-editor depends on — in any way, including moving a dependency's version range — needs the lockfile PR below.

So when moving platform-yalc past a dependency change:

  1. In a paranext-core checkout (with this repo's checkout beside it or in its dev-packages/), run npm install, commit the package-lock.json diff, and open a PR.
  2. Merge that PR together with the platform-yalc push.

Two things check this for you:

  • npm run move-platform-yalc (run from a platform-yalc checkout with a clean tree) is the recommended way to move the branch at all: it resets your local branch to origin's state, rebases onto origin/main, runs this check, and force-pushes only if it passes — so the problem surfaces before the push, not after, and your checkout ends up at exactly the pushed state. (-- --dry-run stops short of pushing; -- --skip-verify is the emergency hatch.)

  • Give it a token when you run it locally. The check reads paranext-core's dev-packages.json and package-lock.json and scans core's recently-updated open PRs, which is tens of API requests against the 60-an-hour budget GitHub gives unauthenticated callers. Inside Actions GITHUB_TOKEN is set for you; locally it is not, so a run can stop partway with a rate-limit error. Authenticated requests get 5,000 an hour:

    export GITHUB_TOKEN=$(gh auth token)

    The check says this itself when it recognises the failure, but exporting it first avoids the round trip.

  • The Verify platform-yalc consumer sync workflow runs the same check on every push to platform-yalc, however the push was made. It passes when paranext-core's main — or an open paranext-core PR touching package-lock.json — matches this branch's dependencies, and fails with these same instructions otherwise. Re-run it after opening the core PR.

Working with the eten-tech-foundation repository

This repo carries the full history of eten-tech-foundation/scripture-editors, so the two share a common ancestry and git can move commits between them. Set it up as a second remote:

git remote add eten-tech-foundation https://github.com/eten-tech-foundation/scripture-editors.git
git fetch eten-tech-foundation

origin stays this repository. Nothing about your existing checkout changes.

Pulling changes from eten-tech-foundation

git fetch eten-tech-foundation
git checkout -b merge-eten main
git merge eten-tech-foundation/main

Resolve any conflicts, then open a PR into main here as usual.

Contributing a change back to eten-tech-foundation

We are not committed to upstreaming routinely, but the option is deliberately kept open. Push a branch to their repo and open the PR there:

git fetch eten-tech-foundation
git checkout -b my-contribution eten-tech-foundation/main
git cherry-pick <commits from this repo>
git push eten-tech-foundation my-contribution

Then open a pull request on eten-tech-foundation/scripture-editors with my-contribution as the compare branch. Because this repository is not a fork, GitHub will not silently offer their repo as the base for PRs opened from origin — you have to target it explicitly, which is the intent.

The committed build output

The dist/ and etc/ folders of packages/platform and packages/utilities are committed, unlike most build output.

paranext-core consumes these packages by copying them out of a checkout rather than installing them from a registry (see Relationship to paranext-core). Committing the build means it needs nothing from this repo's toolchain — no pnpm, no nx, no build step — just to run Platform.Bible. Only people changing the editor build it.

etc/<package>.api.md is API Extractor's report on the public type surface. Nothing consumes it at runtime; it is committed so that a change to the public API — an added export, a changed signature, a removed type — arrives as a readable diff in the pull request that makes it, rather than buried in a rolled-up declaration bundle.

The obligation that comes with both: rebuild and commit them in the same PR as the src/ change. Stale output is invisible in review — the source diff looks right while consumers get old code and the report claims the API did not move.

pnpm rebuild-committed-output   # builds both packages, rolls up their types, writes the reports
git add packages/platform/dist packages/platform/etc packages/utilities/dist packages/utilities/etc

Use that script rather than pnpm nx run-many -t extract-api directly. The two run the same work, but the order the packages are extracted in decides whether each one's rolled-up declarations survive — a sibling's build can overwrite them — and the script fixes the order. scripts/published-packages.mjs explains the mechanism; issue #5 is the fix that will make the order stop mattering.

CI enforces this: it rebuilds and fails if the committed output differs from what the source produces. Every published artifact is byte-deterministic, so a passing check means they genuinely match. *.tsbuildinfo is TypeScript's incremental cache rather than a shipped artifact, so it is neither committed nor packed.

Releasing

Consumers pin a revision of this repository — a branch or tag in dev-packages.json and productInfo.json — rather than a published package version. A release is therefore a repository tag, and one tag pins both packages at once. Tags are named v<platform-editor version>, e.g. v0.8.17, since platform-editor is the package this repo exists to ship.

These packages are not published to npm. paranext-core stages them from a checkout of the pinned revision, copying the dist/ this repo commits; see Relationship to paranext-core.

The flow mirrors paranext-core's:

  1. Decide which branch you are releasing from. For a stable release, rebase release-prep onto main if it has not already been rebased this cycle. For a pre-release, main is normally fine.
  2. Make sure the package versions are what you want to release. If not, dispatch the Bump Versions workflow against that branch, then open a PR from the bump-versions-<version> branch it creates and merge that. The workflow pushes the branch; it does not open the PR, and the target branches are protected.
  3. Dispatch the Publish workflow against the branch you are releasing from. It tags the commit, creates a GitHub release, and — if you give it newVersionAfterPublishing — opens a follow-up bump-versions-<version> branch so later work applies to a new in-progress version rather than to the one just released.
  4. Open a PR from that bump-versions-<version> branch and merge it.
  5. Point consumers at the new tag by setting revision in paranext-core's dev-packages.json. That is the only place the editor revision is named: paratext-10-studio's productInfo.json leaves branch out for devPackageRepos and follows whatever core pins, so pinning a paranext-core release there already pins the editor that release was built with.
  6. If this release changed either package's dependency lists — added, removed, or re-ranged a dependencies, peerDependencies, peerDependenciesMeta or optionalDependencies entry — refresh paranext-core's package-lock.json in the same commit that moves the revision (npm install there, commit the diff). Core stages these packages inside its own tree, so its npm ci validates the staged manifests against its lockfile and aborts on any difference; until the lockfile is refreshed, every core build fails. A release that only moves the packages' own version numbers needs nothing here — npm ci does not check a file: dependency's version. verify-consumer-lockfile-sync.mjs makes exactly this comparison, and runs on every push to platform-yalc.

License

MIT © ETEN Tech Foundation, SIL Global and United Bible Societies

This repository stays MIT-licensed. Platform.Bible itself is moving to AGPL-3.0-or-later; MIT is compatible in that direction, and keeping this repo MIT is also what keeps contributing changes back to eten-tech-foundation/scripture-editors possible.

About

Paratext 10 Scripture Editor

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages