This repository contains a TypeScript implementation for (parts of) the LionWeb specification – specifically: release version 2023.1 of the LionWeb specification.
Note that this repo doesn't implement the specification completely. In particular:
- No support for release version 2024.1 (yet).
- Not all constraints on the LionCore M3 have been implemented.
- The functionality in the
utilitiesandvalidationpackages is provided “as-is”.
The implementation of the JSON serialization format, serialization from in-memory representations to that format, and vice versa, are all pretty solid.
The implementation is divided up in a number of NPM packages in the directory packages (in order of importance) — see their READMEs for more details:
-
jsonEncapsulates the JSON serialization format. -
json-utilsUtilities around the JSON serialization format, also i.c.w. LionCore M3. -
json-diffComputes differences between LionWeb serialization chunks. -
coreThe "core stuff" such as: base types, the LionCore M3 (including thebuiltinslanguage), and (de-)serialization. -
utilitiesUtilities on top of thecorepackages that might be broadly useful, but should not go into thecorepackage. -
validationValidators that validate a JSON serialization. -
ts-utilsGeneral TypeScript utilities, e.g. for working with maps and such. -
textgen-utilsGeneral utilities for doing text – i.e.: code – generation, typically based on thelittoral-templatespackage. -
cliA package with an executable to trigger some of the functionality inutilitiesthrough a commandline interface (CLI), i.e. from the commandline. -
testA package containing (unit) tests for the packages above. -
class-coreA package that contains a framework for the implementation ofINodethat's class-based, and can work with deltas. -
class-core-generatorA package that contains a code generator to generate classes based on theclass-corepackage from an M2. -
class-core-testA package that contains tests that specifically test theclass-corepackage. -
buildA package that builds part of the code inclass-core— specifically the part related to the delta protocol.Note that this package – and specifically the
generate-for-class-core.tsfile – depends onclass-coreitself. This constitutes a circular dependency, but that only exists at compile+build time, so should not be problematic. To ensure that a “clean clone” of this repository is not impacted, therebuild.shscript buildsclass-corefirst, before compiling and runningbuild, and then buildsclass-coreagain. -
artifactsA package that generates artifacts (serialization chunks, diagrams, JSON Schemas) from some of the models constructed in thecoreandtestpackages. -
Various packages related to the delta protocol:
delta-protocol-commondelta-protocol-clientdelta-protocol-low-level-client-browserdelta-protocol-low-level-client-wsdelta-protocol-repository-wsdelta-protocol-test-cliA package that contains CLI programs for starting a client and repository for testing purposes.delta-protocol-testA package that contains tests for the delta protocol implementation.
Each of these packages have their own README.md.
The following packages are published in the scope of the lionweb organization, meaning that they're all prefixed with @lionweb/: json, json-utils, js-diff, core, ts-utils, utilities, cli, and validation, class-core, class-core-generator
The other packages are for internal use only.
All these packages declare their own NPM semver identification, which isn't directly related to the release version of the LionWeb specification.
This repo relies on the following tools being installed:
- Node.js: JavaScript runtime
- NPM (bundled with Node.js)
- A shell (compatible with the Bourne shell), to run
rebuild.shand other.shscripts. (This might take a little more effort on Windows machines than on Linux or even macOS.) - (optional) PlantUML. An IDE plugin such as the one for IntelliJ IDEA also does the trick.
Note that development tends to be done against the latest LTS (or even more recent) versions of Node.js and NPM.
Run the following command to set up the project:
npm run clean
npm install
npm run setupThe chain of preceding commands can also be run as follows:
npm run initializeRun the rebuild.sh script (re-)build (“make”) each of the packages, in dependency order.
This script exits – or at least: should – as soon as the first failure it detected.
It also triggers the generate scriptlet of the build package, which generates a couple of source files in other packages from various sources across this repo.
It's necessary to run this script when these sources have changed, or when the code of the class-core has changed significantly.
Note that there a cyclic dependency between the class-core and build packages, which sometimes necessitates running this script twice to arrive at a stable state.
(The rebuild scriptlet in the top-level package.json runs (only) the rebuild.sh script.)
Run the following command to just com-/transpile the TypeScript source code in all packages:
npm run build(This is typically enough.)
Run the following command to run all the tests:
npm testThe output should look similar to this (but much longer):
<br />
<br />
<img src="./documentation/images/test-output.png" alt="test" width="50%"/>The following command statically style-checks the source code in all the packages:
# Run lint
npm run lintNote that this does not catch TypeScript compilation errors! (That's because linting only does parsing, not full compilation.)
To keep the version numbers of the various packages under packages/ aligned throughout this repository, you use the Node.js script update-package-versions.js.
You execute this script as follows from the repo's root:
./update-package-versions.jsThis reads the file packages/versions.json and updates the package.json files of all workspace packages (as listed in the root-level package.json) under packages/ according to it, as well as the main(/root-level) package.json.
The format of that versions.json file is self-explanatory.
This script runs npm install afterward to update the package-lock.json.
Inspect the resulting diffs to ensure correctness, and don't forget to run npm install to update the package-lock.json in case you made corrections outside of/after running this script.
Packages are released to the npm registry (website): see the badges at the top of this document.
We'll use the terms “release/releasing” from now on, instead of “publication/publishing” as npm itself does.
We (only) release the following packages: core, validation, utilities, cli, class-core, class-core-generator, ts-utils, textgen-utils, io-lionweb-mps-specific.
Releasing a package involves the following steps:
- Update the version of the package to release in its own
package.json.- Also update all references to that package in any
package.jsonin the other packages. - Ensure that the Changelog section of the package to release has been updated properly and fully.
- Run
npm run initializeto updatepackage-lock.jsonand catch any (potential) problems. - Commit all changes to the
mainbranch — if necessary, through a PR.
- Also update all references to that package in any
- Run the
releasescript of the package:This requires access as a member of thenpm run release
lionweborganization on the npm registry — check whether you can access the packages overview page. This step also requires a means of authenticating with npm, e.g. using the Google Authenticator app. - Tag the commit from the 1st step as
<package>-<version>, and push the tag. - Update the version of the released package to its next expected beta version, e.g. to
0.7.0-beta.0.- Run
npm run initializeto updatepackage-lock.jsonagain. - Commit all changes to the
mainbranch — if necessary, through a PR.
- Run
Note that beta releases are different in a couple of ways:
- Beta releases have versions of the form
<semver>-beta.<beta sequence number>, e.g.:0.7.0-beta.0. - They are released using the
release-betascripts.
Releasing all (releasable) packages at the same time can be done through the top-level release script.
If you do that, you can perform the manual steps above all at the same time, which might save time and commits.
You can also perform an alpha release in exactly the same way as a beta release, but with all occurrences of "beta" replaced with "alpha".
Alpha releases should be limited to experimental features.
Run the NPM task check-circular-dependencies to check whether circular dependencies exist in any of the packages.
A circular dependency is a cycle in import statements in TypeScript code.
Such circular dependencies don't necessarily prevent the code from being compilable and runnable, but problems can arise due to web bundlers, and in debugging.
Circular dependencies can usually be avoided by using the “internal module pattern”, which is explained in this blog.
The TL;DR of that is:
- Export all internally-exposed stuff from a central
index-internal.ts. - Then, import from that file only.
- Export everything you want exposed to the outside world from a
index.tswhich imports fromindex-internal.ts.
Currently, we're not using a tool like changesets – including its CLI tool – to manage the versioning and release/publication.
That might change in the (near-)future, based on experience with using changesets for the LionWeb repository implementation.
All the code in this repository is written in TypeScript, with the following code style conventions:
-
Indentation is: 4 spaces.
-
No semicolons (
;s). This is slightly controversial, but I (=Meinte Boersma) simply hate semicolons as a statement separator that's virtually always unnecessary. The TypeScript compiler simply adds them back in the appropriate places when transpiling to JavaScript. -
Use "FP-lite", meaning using
Array.mapand such functions over more imperative ways to compute results.
We use prettier with parameters defined in .prettierrc.
Note that currently we don't automatically run prettier over the source code.
If you prefer not to install the development dependencies on your machine, you can use our containerized development environment for the LionCore TypeScript project. This environment provides a consistent and isolated development environment that is easy to set up and use. To get started, follow the instructions in our containerized development environment guide. However, you can streamline the process by running the following command:
docker run -it --rm --net host --name working-container -v ${PWD}:/work indamutsa/lionweb-devenv:v1.0.0 /bin/zshdocker run: Initiates a new container.-it: Enables interactive mode with a pseudo-TTY.--rm: Removes container after exit.--net host: Shares the host's network.--name working-container: Names the container.-v ${PWD}:/work: Maps host's current directory to/workin the container.indamutsa/lionweb-devenv:v1.0.0: Specifies the Docker image./bin/zsh: Starts a Zsh shell inside the container.
We're happy to receive feedback in the form of
- Issues – see the issue tracker.
- Pull Requests. We generally prefer to squash-merge PRs, because PRs tend to be a bit of a "wandering journey". If all commits in a PR are essentially "atomic" (in a sense that's at the discretion of the repo's maintainers), then we can consider merging by fast-forwarding.
- Join the LionWeb Slack!