floco is a JavaScript package management and build tool powered by
Nix.
floco is a bold departure from conventional JavaScript package management
tooling focusing on reproducibility, distributed caching, and sandboxing.
Every package is built in a strictly declared sandbox isolated from the runtime
system, much like an OCI container.
This approach allows packages to be built once and reused on any system that
shares the same architecture and platform.
Artifacts are cached locally and may be distributed among a cluster of systems
allowing development environments to be created at more than twice the speed of
npm or yarn.
Underlying installers are implemented in bash and limit themselves to using
coreutils, findutils, and jq improving reproducibility and readability
compared to tooling implemented using a sprawling maze of JavaScript.
Despite this repository’s seemingly small form, it is the result of nearly
a year of exploration, trial, and refinement.
A great deal of effort was expended to make this piece of software
suck less than the competition, but rest assured that these routines were
not implemented naively.
Writing a JavaScript package management framework in bash and nix was
done not because it is easy - on the contrary it was fucking hard.
There’s a dedicated Getting Started guide that is the best place to dive in.
For projects that depend only on registry packages without install scripts that require
native dependencies or cycle breaking,
the following process will have you up and running:
set -euo pipefail;
cd ./my-project;
nix flake init -t github:aakropotkin/floco;
nix run github:aakropotkin/floco -- translate;
[[ -r ./package-lock.json~ ]] && mv ./package-lock.json{~,};
nix build -f ./. global;
ls -R ./result;A collection of documentation is hosted on GitHub on the project’s wiki tab, and is also available under <floco>/doc alongside many of the workspace directories used in examples.
Additionally all CLI tooling and scripts support the --help option.
The floco help CMD sub-command may also be used to view “help” messages
for a given CMD.
A simple template with the boilerplate needed for use with our updaters
is available through nix flake init -t github:aakropotkin/floco.
More templates are on the way.
The floco CLI interface is currently under active development and is
expected to change rapidly in the near future.
Across the floco CLI a few behaviors are consistent.
The following config files are included/applied if they exist:
/etc/floco/floco-cfg.{nix,json}${XDG_CONFIG_HOME:-$HOME/.config}/floco/floco-config.{nix,json}- “Local”
floco-config.{nix,json}searched for betweenPWDand git project root, or/ifPWDis not agitrepository checkout.
References to floco will be pulled from the nearest flake.lock, or
nix registry list, using github:aakropotkin/floco/main as a fallback.
Generate a pdefs.nix file from a package[-lock].json or
registry package.
This routine utilized npm internally to resolve packages and form
node_modules trees.
mkdir -p /tmp/foo;
pushd /tmp/foo;
echo '{
"name": "@floco/phony",
"version": "4.2.0",
"dependencies": {
"lodash": "^4.17.21"
},
"scripts": {
"build": "touch ./built"
}
}' > ./package.json;
nix shell github:aakropotkin/floco;
floco -- translate -pt;
nix flake init github:aakropotkin/floco -t;
floco build; # or `nix build -f ./. global;
ls ./result/lib/node_modules/@floco/phony/;mkdir -p /tmp/foo;
pushd /tmp/foo;
nix shell github:aakropotkin/floco;
floco -- translate -pt lodash@4.17.21;
nix flake init github:aakropotkin/floco -t registry;
echo '{ ident = "lodash"; version = "4.17.21"; }' > ./info.nix;
floco build; # or `nix build -f ./.;'
ls ./result/lib/node_modules/lodash/;List all declared projects by “key”, being <IDENT>/<VERSION>, such as
@foo/bar/4.2.0 or baz/4.2.0.
nix run github:aakropotkin/floco -- list;@webassemblyjs/wast-printer/1.9.0 @xtuc/ieee754/1.2.0 @xtuc/long/4.2.2 abab/2.0.6 abbrev/1.1.1 abbrev/2.0.0 abort-controller/3.0.0 accepts/1.3.8 acorn/6.4.2 acorn/7.4.1 acorn/8.8.2 acorn-globals/4.3.4 acorn-jsx/5.3.2
Print the pdef record for a given package.
nix run github:aakropotkin/floco -- show lodash@4.17.21 --json;{
"ident": "lodash",
"version": "4.17.21",
"ltype": "file",
"fetchInfo": {
"narHash": "sha256-amyN064Yh6psvOfLgcpktd5dRNQStUYHHoIqiI6DMek=",
"type": "tarball",
"url": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
},
"treeInfo": {}
}
floco uses a small collection of bash scripts to perform install tasks
and drive builds.
These scripts do not depend on Nix, and are suitable for standalone use
as replacements for pacote extract ( install-module.sh ) and
(npm|yarn) run ( run-script.sh ).
You can install these as standalone executables using the floco-utils
installable in the top-level flake.
For example usage and more details please see Core Scripts.
The top level flake provides an installable floco-updaters as well as
app targets ( fromPlock and fromRegistry ) that can be used to generate
pdefs.nix and pdefs.json files to be loaded by as configs.
These scripts will allow you to convert existing JavaScript projects to be
used with floco, and update/regenerate configs as projects’
dependencies and build requirements change.
This generator is intended for use with local projects.
It is essentially a wrapper around npm i --package-lock-only.
For example usage please see the Getting Started guide.
This generator is intended for use with published registry packages that
you’d like to make accessible to floco and nix.
This script behaves almost identically to fromPlock, except that it
ignores devDependencies entirely, and accepts package descriptors as an
argument ( as npm or yarn would ).
For example usage please see the Native Dependencies guide.
This script most useful for packaging executables and generating treeInfo
information for packages that have install scripts ( such as node-gyp
compilation ).
Package metadata collection, also called translation, and project composition is managed using Nixpkgs Modules similar to those used by NixOS, dream2nix, or home-manager.
These modules are organized as sets of interface.nix and
implementation.nix files and are designed to be extensible.
The core of the module system revolves around a record called pdef, short
for “package definition”, which organizes translated metadata, and
package records which organize the build pipelines.
This separation simplifies the organization of the translation and builder APIs, but the rationale runs further. The split allows us to flatly state: build routines must never perform impure operations, and translation routines must only produce fields that can be serialized to JSON.
Serialization of translated metadata allows Nix’s flake features to
drastically improve performance by leveraging
eval caching to avoid
re-evaluation of recipe generation on successive runs.
The pdef record closely mirrors the pseudo-standard schema used by most
package.json files; but is much stricter about how declarations
are written.
If desired, users could ditch package.json files altogether and simply
write pdef records for their projects.
At time of writing only a few translators have been migrated from the alpha iteration, at-node-nix, but in the near future these will be finalized for production use.
This is our bread and butter, and serves as the default implementation for
creating a pdef record.
On its own this translator would require users to explicitly declare the
structure of their node_modules/ tree using the treeInfo submodule.
For this reason we strongly recommend using the package-lock.json
translator for projects with large dependency graphs.
The term ideal tree refers to the mapping of a node_modules/ tree
from a dependency graph.
This process is by far the most complex and challenging aspect of
Node.js package management.
While floco currently relies on npm to generate ideal trees, this
is expected to end soon.
The alpha repository
at-node-nix contains a
large body of routines to perform best effort treeInfo
mapping, specifically handling projects which only require a single
version of any package ( this property is called The Golden Rule in
package management contexts ).
Additionally the semver resolution routines used to fetch closures of packument records effectively solve half of the ideal tree process, leaving only scope and follows management to be completed.
This is by far the most developed translator, and is the recommended option for large projects.
This translator will automatically fill treeInfo submodules, and
triggers minimal network fetching.
A rudimentary translator exists to collect information from yarn.lock
v5 ( produced by yarn v3 ), but because these lockfiles lack
ideal tree information users will need to provide treeInfo themselves.
In the future we intend to produce treeInfo from these locks using
the pinned version information they contain; but this routine still needs
to be authored.
A CLI frontend for the npm ideal tree routine,
arborist,
modified such that package-lock.json files can be emitted to STDOUT
without modifying the project.
This is expected to be used in later iterations of the updaters allowing
them to be run on /nix/store/ paths.
The builtins.npmLock example in the section takes advantage of this.
This executable is exposed as an installable and app in the
top-level flake.
A nix plugin for use with nix --plugin-files ... is available in the
top level flake, along with a wrapper executable, floco-nix, which
automatically loads this plugin.
In the future this plugin is expected to grow into a full executable that
provides a suite of CLI commands; but for now it accepts nix arguments
and sub-commands.
This plugin was developed for Nix v2.12.0, but is likely compatible with a wider range of versions.
Our plugin adds a few new builtins to the nix evaluator which are
useful for dynamically generating package definitions.
Wraps npm show allowing Nix to query package registries using a users
existing npm config and any environment NPM_CONFIG_* variables.
While floco is already able to fetch package registry information
without any external tools; this builtin is useful for accessing private
package registries and inheriting authorization settings with
minimal setup.
nix run github:aakropotkin/floco#floco-nix -- eval --json --expr '
builtins.attrNames ( builtins.npmShow "lodash" )
'|jq;[ "_cached", "_contentLength", "_hasShrinkwrap", "_id", "_nodeVersion", "_npmOperationalInternal", "_npmUser", "_npmVersion", "_rev", "author", "bugs", "contributors", "description", "directories", "dist", "dist-tags", "gitHead", "homepage", "icon", "keywords", "license", "main", "maintainers", "name", "readmeFilename", "repository", "scripts", "time", "users", "version", "versions" ]
Resolves package descriptors such as foo@^1.0.0 or bar@latest
using npm, returning a resolved URI.
This has the same environment and configuration properties as npmShow.
NOTE: if you use ranges such as lodash@2.x you will want to use
builtins.split to parse the output.
nix run github:aakropotkin/floco#floco-nix -- eval --expr '
builtins.npmResolve "lodash@latest"
';"https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
Produces a virtual package-lock.json for a given project path
without modifying the project or making any writes to the filesystem.
This is an ideal alternative to the fromRegistry updater when
used in combination with builtins.fetchTree and builtins.npmResolve.
In practice you can dynamically generate full dependency closures’
treeInfo records using this routine.
I currently use it for this purpose out in the field; but have avoided
using it in the default modules so that they are usable without plugins.
nix run github:aakropotkin/floco#floco-nix -- eval --impure \
--expr 'let
url = builtins.npmResolve "pacote@latest";
src = builtins.fetchTree { type = "tarball"; inherit url; };
plock = builtins.npmLock src;
in builtins.attrNames plock
';[ "lockfileVersion" "name" "packages" "requires" "version" ]
Runs node-semver to test whether a semantic version satisfies
a constraint.
In the future node-semver will be replaced using a native C++ port
semi.
This largely exists as a stop-gap until the pure nix implementation
from the alpha repository is polished and/or semi is completed.
nix run github:aakropotkin/floco#floco-nix -- eval --expr '[
( builtins.semverSat "^4.2.0" "4.0.0" )
( builtins.semverSat "^4.2.0" "4.2.0" )
( builtins.semverSat "^4.2.0" "4.2.1" )
( builtins.semverSat "^4.2.0" "4.3.0" )
]
';[ false true true true ]
Many of the following extensions have function drafts or well tested
prototypes in the alpha release of floco; but are not developed enough for
use in production code-bases as pieces of reliable infrastructure.
- Improved support for package.json workspaces.
- Currently reliance on
npmand special configuration based on in depth knowledge offlocois necessary to accomplish workspace support. - Practically a template or example using workspaces is likely sufficient for the immediate future; but the NixOS Module system is expected to resolve issues that previously made workspaces complex to manage.
- Currently reliance on
- Expanded CLI tooling.
- Currently users are asked to interact with nix to drive builds, tests,
update metadata, etc.
Ideally a simple bash script would provide familiar commands such as
floco add <PKG>,floco publish,floco update,floco build, etc thatnpmandyarnusers are already familiar with.
- Currently users are asked to interact with nix to drive builds, tests,
update metadata, etc.
Ideally a simple bash script would provide familiar commands such as
- Nix plugin to read/write caches globally and into
flake.lock.- This is the real end goal for
floco. It should be possible to read/writeflocometadata toflake.lockand existingnixcaches. - There is currently a draft plugin which allows nix to adopt
npmURIs to refer to packages aslodash@4.17.21which could be expanded upon. - Project templates and propagation of build recipes could allow
nixto abstract away the generation offlake.nixfor the vast majority of projects which would be a significant UX breakthrough.
- This is the real end goal for
- Semantic version parsing, and ideal tree formation.
- Currently
flocoreally relies onnpmand itspackage-lock.jsonto construct non-trivial node_module/ metadata declarations. This reliance is a major pain point for handling projects which currently use yarn since interoperability betweenyarnandnpmacross their associated lockfiles is implemented incredibly poorly, to such a degree that you cannot trust them to behave predictably in the same source tree. - Semver parsing and solving SAT is implemented in the alpha repository, and
has been testing on large non-trivial inputs quite successfully.
Still this effort requires a few weeks of polishing to really approve for
use in production.
- For now we have provided
node-semver as an
installable in the top-level flake for use in scripts and our
floco-nix
through
builtins.semverSat.
- For now we have provided
node-semver as an
installable in the top-level flake for use in scripts and our
floco-nix
through
- Construction of ideal tree from semver SAT is a project in and of itself
in order to support things like
optionDependencies,peerDependencies,bundledDependencies, and other oddballs which are a prerequisite for use in the general case.
- Currently
Sadly IRC is dead. IRC remains dead. So like most folks these days we use Matrix Chat.
Space: #floco:matrix.org
General Room: https://matrix.to/#/!wMSeevIIjIbAOVbqHh:matrix.org?via=matrix.org ( Recommended )
Support Room: https://matrix.to/#/!tBPFHeGmZfhbuYgvcw:matrix.org?via=matrix.org
Development Room: https://matrix.to/#/!qDFpEnHkbpkhLSenko:matrix.org?via=matrix.org
floco was originally developed for use by Tulip Interfaces.
Without their support this project never would have been possible.