Skip to content
/ wool Public

A typescript ecosystem - package manager, decentralised registries, monorepo compiler

License

Notifications You must be signed in to change notification settings

woolts/wool

Repository files navigation

Wool

This project is very early pre-alpha, not everything described here exists, and much of it will not work as expected.

Wool is a new typescript ecosystem designed for a great developer experience. Easy for beginners and scalable for advanced projects.

It is a package manager, decentralised package registry, and monorepo compiler.

With typescript only packages and automated semantic versioning it brings an extra level of stability to your projects.

Many of the early ideas were described in this article about a better javascript ecosystem, though it has since evolved, notably becoming purely typescript.

Wool is self compiling, so you can look at this repo to see how a project can be structured. For a simpler example see the examples directory.

Wool runs on a zero config philosophy, developers shouldn't need to worry about configuring their compilers, be that webpack or tsc, they should only be concerned with writing the code for their projects. To that end, the only configuration possible is naming, versioning, dependencies and structuring your workspaces.

All packages are installed into a single directory, ~/.wool/packages. Once you've installed a package on your machine, it doesn't need to be installed again, or copied to another location, or even symlinked. All packages are imported directly from this single directory. Starting a new project with packages you've already installed will be instantaneous.

Getting Started

While in development the installation step is manual. Go to the releases and follow the instructions.

To test it is working, run:

wool list --global

And you should see a list of the installed wool/* packages.

Unless stated on a new release, to upgrade you should be able to simply clone this repo and run wool make . inside it. This will install the latest code within each package into ~/.wool/packages.

Creating a New Project

In the future you will be able to do this by running wool init, but for now you can follow these instructions.

In a new directory create a wool.json file:

{
  "name": "lsjroberts/my-project",
  "entry": "index.ts",
  "dependencies": {}
}

Note the package name, lsjroberts/my-projects, all packages must be namespaced. (The only exception to this are node builtins that keep their non-namespaced names).

In your entry file write your code:

// index.ts
export default () => console.log('Hello, World!');

Then compile your project:

wool make .

It will compile it into your wool-stuff/build-artifacts directory, so it is best to add a .gitignore:

# Generated files
wool-stuff
tsconfig.json

# Editors
.vscode

# System
.DS_Store

Note how we ignore the generated tsconfig.json files. These are created each time you run wool make . and will contain paths specific to your machine, so you should not commit them to your project.

Workspaces

Wool takes monorepos to the next level. You can have as many nested shared or independently versioned packages as you like in a single repo.

Running wool make . in the root directory will then build all your packages and link them together as needed. You don't need to worry about which node_modules directory a package is being imported from, it just works. In fact, there are no node_modules directories.

To add workspaces to your project, update your wool.json:

{
  "private": true,
  "version": "1.0.0",
  "workspaces": [
    "workspaces/my-package",
    "workspaces/another-package",
    "some/other/directory/a-third-package"
  ]
}

Each of your workspaces should then have their own wool.json:

// workspaces/my-package
{
  "name": "lsjroberts/my-package"
}

In this case, its version is determined by the root version.

If you wanted, you can have a root version, then another set of packages with their own shared version, and yet more packages that are independently versioned.

{
  "private": true,
  "workspaces": [
    "independent/my-package-one",
    "independent/my-package-two",
    "shared"
  ]
}
// shared
{
  "private": true,
  "version": "2.3.0",
  "workspaces": ["my-package-three", "my-package-four"]
}
// shared/my-package-three
{
  "name": "lsjroberts/my-package-three"
}

CLI

Every command has the following options:

  • --help - Display detailed help on usage (todo)
  • --dry-run - Disables all side effects and outputs the actions that would have occurred (todo)

Compile a project (✓)

Compiles modified workspaces in the given directory into ./wool-stuff/build-artifacts, and copies successful packages into your ~/wool directory ready to be used by any project on your machine.

wool make .

Wool will detect the compilation order for your packages based on their dependency tree.

Run a package (wip)

Instead of polluting your global list of binaries on your machine, you can execute the entry file of any installed wool project through wool.

(todo)

wool run lsjroberts/example

Run a specific version (✓)

wool run lsjroberts/example/1.1.0

Run a private package (✓)

If you have compiled a package that is marked as private, it will not be installed into your ~/.wool directory, and therefore can not be run with wool run.

To run this package use:

wool run-private lsjroberts/private-example

This will run it from your ./wool-stuff/build-artifacts directory.

Note, you do not need to provide a version number.

Pack a workspace into gzipped bundles (✓)

Bundles each package into ./wool-stuff/bundles/lsjroberts_example-1.0.0.tar.gz.

Usually you will not need to run this command directly, but it can be helpful to do so if you wish to debug the bundle that will be published to your registries.

wool pack .

Add a dependency (wip)

wool add lsjroberts/package

Searches and installs packages, in order until a matching package and version is found:

  1. Offline first, from ~/.wool/packages
  2. Previously specified registry for the package in wool.lock
  3. Registries in order listed in wool.json

Use --offline to only look for the package in the existing installed packages, skipping steps 2 and 3.

Use --fresh to ignore the wool.lock and search for the package online in registry order.

Use --workspace / -w to add the package into a specific workspace other than the current directory.

wool add lsjroberts/package -w workspaces/example

Run a local task (wip)

{
  "tasks": {
    "test": "wool run example/test ."
  }
}
wool task test
{
  "tasks": {
    "test": "wool run example/test .",
    "build": {
      "test": "wool task test",
      "make": "wool make ."
    }
  }
}
wool task build
wool task build.make

Install a global dependency (todo)

wool install lsjroberts/package

Installs a package but does not add it to the local project. Its binaries are symlinked from the ~/.wool/.bin directory.

e.g.

{
  "name": "lsjroberts/package",
  "version": "1.0.0",
  "bin": {
    "example": "./bin/example.sh"
  }
}
wool install lsjroberts/package
ls ~/.wool/.bin/example
> ~/.wool/.bin/example -> ../packages/lsjroberts/package/1.0.0/bin/example.sh

Testing

A test suite can be created as a workspace, for example the workspaces/example/src would have its tests in workspaces/example/tests, each with their own wool.json and dependencies.

We can then add a test task to the parent workspace:

// workspaces/example/wool.json
{
  // ...
  "tasks": {
    "test": ["wool", "run-private", "wool/example-tests"]
  }
}
// workspaces/example/tests/index.ts
import { assert, describe, run } from 'wool/test';

const suite = describe('example', [
  assert({
    given: 'an example',
    should: 'return true',
    actual: true,
    expect: true
  }),
]);

run(suite);

See workspaces/test for further information.

See workspaces/core for a working example.