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 betweenPWD
and git project root, or/
ifPWD
is not agit
repository 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
npm
and special configuration based on in depth knowledge offloco
is 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 thatnpm
andyarn
users 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/writefloco
metadata toflake.lock
and existingnix
caches. - There is currently a draft plugin which allows nix to adopt
npm
URIs to refer to packages aslodash@4.17.21
which could be expanded upon. - Project templates and propagation of build recipes could allow
nix
to abstract away the generation offlake.nix
for 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
floco
really relies onnpm
and itspackage-lock.json
to construct non-trivial node_module/ metadata declarations. This reliance is a major pain point for handling projects which currently use yarn since interoperability betweenyarn
andnpm
across 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.