The workspaces are divided among a few top-level directories.
Be sure to check out the CONTRIBUTING.md file in the workspace where
changes are being made, as this may provide contribution guidance
specific to that component.
These workspaces are all are direct dependencies of the vlt CLI.
The actual CLI is also a workspace in src/cli-sdk.
Most of these are also published separately under the @vltpkg scope.
These workspaces are internal tools that we use for tasks like building, bundling, and benchmarking the other workspaces.
These are internal and highly coupled to our monorepo setup, so they are not published to any registry.
This directory also contains all the cli-* variants.
These are websites that get deployed. Currently only docs.vlt.sh.
Run vlr fix to report any formatting or linting issues with your
code, and to attempt to fix them.
If you are on the latest version of Node, you can run TypeScript files
directly using node ./path/to/file.ts.
If you are on Node 22, you can use node --experimental-strip-types.
For both of those options you will see a warning. If that bothers you
then you can run with --no-warnings as well.
In order to make this work for all places where we might spawn a node
process, run
export NODE_OPTIONS=--experimental-strip-types --no-warnings to set
this in the environment, rather than on each command.
If you use nave then you can enable nave auto
behavior in your bash profile by adding something like this:
# .bashrc or .bash_profile or wherever you put these things
__nave_prompt_command () {
if nave should-auto; then
exec nave auto
fi
}
export PROMPT_COMMAND="__nave_prompt_command || true; ${PROMPT_COMMAND}"Then, the appropriate node version and NODE_OPTIONS flags will be
set to be able to always run TypeScript files directly in the context
of this project.
If you use nvm, a compatible
version of node should be automatically installed & used as defined
in our project's root .nvmrc file. Notably, there are known issues
in node versions <22.14 and unknown compatibility for >=23. If
you are using a version outside of the known-good range set you are
likely to experience errors when developing and should install an
in-range version.
Our CI uses the setup-node action and is explictely configured to
use the "latest", known-good version of node (^22.14.0) available.
You can find this configuration in .github/workflows/*.yml.
This root of this repo has scripts for each named bin that can be run
via vlt for testing locally.
These scripts set the correct NODE_OPTIONS to run the TypeScript
source directly.
$ node --run vlt -- --version
0.0.0-0A directory of sh and ps1 executables is located at
./scripts/bins. These call the TypeScript source bin files with the
correct NODE_OPTIONS. This directory is designed to be put at the
beginning of your path temporarily to make running of vlt and its
related CLIs run directly from source.
export PATH=~/projects/vltpkg/vltpkg/scripts/bins:$PATH
vlt --versionThere are also directories in ./scripts/bins for each variant of the
CLI. These directories can be used the same way to manually run the
following:
./scripts/bins/bundle: Theesbuildbundled JavaScript withnode./scripts/bins/compile: The compiled variant for the current platform
All workspace directories are designed so vlt publish can be run
from that directory.
On all pushes to main a GitHub Actions workflow will run to create a
release PR. That PR will be updated for all subsequent pushes. The PR
will contain any release related commits, usually just the bumping of
package.json version numbers.
When that PR is merged the same workflow will then publish the bumped packages.
The weekly release manager's role is to merge the release PR. Pretty simple 😄
The release PR will be created as a draft. When it is time to
release, merging other PRs should be temporarily paused and the
release PR should be:
- Marked as ready for review
- Approved
- Merged (Make sure to use "Rebase and merge")
The release PR will run additional checks not run on normal PRs. Those include:
- Running all workspace tests
- Running workspace tests in additional environments
- Running the smoke-tests
If these status checks fail unexpectedly, it could be because its flaky or its a real bug. You should check the workflow logs to determine which case it is.
View the latest release run
gh run view $(gh run list -w "ci.yml" -b release -L 1 --json databaseId -q ".[].databaseId") -wRerun the failed jobs
gh run rerun $(gh run list -w "ci.yml" -b release -L 1 --json databaseId -q ".[].databaseId") --failedOnce the release PR is merged, a publish will be triggered from CI. If this workflow fails (due to network, auth, etc issues) it can be safely rerun. It will only attempt to publish workspaces which have not had their current version published.
Rerun the publish job
gh workflow run release.yml --ref=main -f action=publishThe following packages can be published as part of the CLI. Which
packages are published is dependent on the value of
PUBLISHED_VARIANT in
@vltpkg/infra-build.
vltThis will either be the JS or compiled variant of the CLI.@vltpkg/cli-jsThe JS variant of the CLI. This is always published and is helpful for testing/debugging vs the compiled variant.@vltpkg/cli-compiledThe is the compiled variant of the CLI. This package only has placeholder bins that are swapped out in a postinstall for one of the platform variants below.@vltpkg/cli-darmin-arm64@vltpkg/cli-darmin-x64@vltpkg/cli-linux-arm64@vltpkg/cli-linux-x64@vltpkg/cli-win32-x64
Note that the platform specific variants do not have any
package.json#bin entries because that is incompatible with the
postinstall strategy of the parent package. If you need to install one
of those directly, you will need to move/run/link the included vlt
executable manually.
The bundled JS variant of the CLI exists in the infra/cli-js
directory, because it is still published for manual testing and
comparisons and it is an intermediary of the compiled CLI, so it gets
tested in smoke-tests to help debug the build pipeline of
TS source -> esbuild bundled JS -> compiled.
The compiled variant of the CLI exists in the infra/cli-compiled
directory, because the end goal is to publish this as the default
vlt package. For now it is published under a scoped name for manual
testing.
When run locally the GUI can be set to use live reload so that any
changes to GUI source code will cause the browser to reload. To enable
this set __VLT_INTERNAL_LIVE_RELOAD=1 when running the GUI:
__VLT_INTERNAL_LIVE_RELOAD=1 ./scripts/bins/vlt guiWhen publishing, all of the source code is first bundled and code-split with esbuild.
There are some values in the source code that aren't statically analyzed by esbuild, and instead are read from environment variables. This is to make it explicit and because previous attempts to parse the AST and detect those values were slow and brittle.
All the environment variables follow the pattern __VLT_INTERNAL_* in
order to distinguish them from the VLT_* environment variables that
are set by the CLI's config system.
Building for publishing is handled by the
infra/build workspace.
There are some root level scripts that can be run to generate these builds for testing locally.
# creates a directory with all the bundled JS
vlr build:bundle
# creates a directory with the compiled binaries
# for the current os and cpu
vlr build:compileTo generate compiled builds for other platforms use the --platform
and --arch flags.
vlr build:compile -- --platform=win32 --arch=x64You can also run vlt pack in any of the ./infra/cli-* directories
to generate a tarball of the build.
knip is installed and configured in the root of the monorepo and can
be run with:
vlr knipIt is not currently turned on in CI but can be helpful to run locally after a refactor or moving/creating/deleting workspaces.
False positives can be ignored in the knip.ts config file.
If you are testing a file that has side effects when imported, such as
reading from process.platform to run different code, then this file
can't be imported with a static import and instead must use
t.mockImport for all instances.
Even though this code is valid and the tests will pass, coverage will most likely fail depending on the implementation.
// ❌ dont do this
import { getEnvValue } from '../src/index.ts'
t.test('default', async t => {
t.equal(getEnvValue(), 'DEFAULT_VALUE')
})
t.test('other case', async t => {
t.intercept(process, 'env', { value: { MY_VALUE: 'MY_VALUE' } })
const { getEnvValue } = await t.mockImport('../src/index.ts')
t.equal(getEnvValue(), 'MY_VALUE')
})Instead, all instances of the import must come from t.mockImport:
// ✅ Do this
t.test('default', async t => {
const { getEnvValue } = await t.mockImport('../src/index.ts')
t.equal(getEnvValue(), 'DEFAULT_VALUE')
})
t.test('other case', async t => {
t.intercept(process, 'env', { value: { MY_VALUE: 'MY_VALUE' } })
const { getEnvValue } = await t.mockImport('../src/index.ts')
t.equal(getEnvValue(), 'MY_VALUE')
})