External collections
By default, builder execute reads project-builder.json — and every collection, factory and
schema it registers — from the working directory. --manifest points those reads somewhere
else on the same machine, while generated files still land in the working directory.
The motivating case is git worktrees: your schematics are not committed to the
application repository, so a worktree created next to the main checkout does not have them.
With --manifest, every checkout runs the same schematics from one place.
Two roots
Section titled “Two roots”| Root | What it is | What it is used for |
|---|---|---|
| Manifest root | The directory named by --manifest (or the working directory when it is not given) |
All reads: project-builder.json, collection.json, factories, schema.json, templates. Paths registered in the manifest resolve against this directory. |
| Workspace | The working directory | All writes: generated files, and the @pbuilder/sdk the run uses |
Without --manifest both roots are the working directory, which is why you normally never
notice the difference.
--manifest and BUILDER_MANIFEST accept a path on this machine — nothing else. Any value
that starts with a URL scheme (https://, git://, ssh://, file://) or a drive letter
is rejected. Nothing is fetched, cloned or cached.
Set up a shared collections directory
Section titled “Set up a shared collections directory”Keep the schematics in a directory of their own, outside the application checkouts, with
no node_modules in it or above it:
~/work/├── app-schematics/ ← manifest root│ ├── project-builder.json│ └── schematics/│ └── hello/│ ├── factory.ts│ ├── schema.json│ └── schema.generated.ts├── app/ ← main checkout└── app-feature-x/ ← git worktree of app{ "collections": { "default": { "hello": { "path": "./schematics/hello" } } }}A factory resolves @pbuilder/sdk from its own location first. If the manifest root — or any
directory above it — has a node_modules/@pbuilder/sdk, the factory loads that copy while
the run uses the workspace’s copy. Two copies are never mixed: the run fails with
engine_native_developer_fault and a note about a split module graph, even when both
copies are the same version.
This is why schematics kept inside the main checkout, which has its own node_modules,
cannot be run from a worktree with --manifest. Move them to a directory of their own.
-
Install the SDK once, outside the checkouts. It must be version 0.3.1 or later: older versions cannot be loaded by a factory outside the workspace.
Terminal window bun add -g @pbuilder/sdkThe package directory this creates,
~/.bun/install/global/node_modules/@pbuilder/sdk, is the SDK root. For other global installs, see what the root must be. -
Run the schematic, naming both roots.
--manifestand--sdk-rootgo before<collection>:<schematic>:Terminal window cd ~/work/app-feature-xbuilder execute --manifest=../app-schematics \--sdk-root=$HOME/.bun/install/global/node_modules/@pbuilder/sdk \default:hello --name=paymentsThe factory and its schema are read from
~/work/app-schematics; the generated file is written to~/work/app-feature-x/src/services/payments.ts. The checkout needs nonode_modulesof its own: the only other entry the run writes isnode_modules/@pbuilder/sdk, a link to the SDK root. -
Inspect the collection the same way:
Terminal window builder info --manifest=../app-schematics default
The same command works from the main checkout — every checkout, worktree or not, uses the shared directory and the shared SDK.
The order in which execute picks an SDK is in
SDK source. Two cases to watch:
- A checkout that already has a real
node_modules/@pbuilder/sdkfails withexecute_sdk_link_path_conflictwhen you name a different root. Either remove that install, or run that checkout without--sdk-rootand withBUILDER_SDK_ROOTunset, so the run uses it. - A
node_modulesthat is a symlink to another checkout is rejected as escaping the workspace, with or without--sdk-root.
How the value is read
Section titled “How the value is read”- A directory or the file itself.
--manifest=../app-schematicsand--manifest=../app-schematics/project-builder.jsonare equivalent. - Relative or absolute. A relative value resolves against the working directory. The path is canonicalised once, following symbolic links.
- Naming the working directory is the same as not passing the flag.
- Placement. For
execute,--manifestmust come before<collection>:<schematic>— after it, it is rejected withinvalid_inputinstead of being passed to the schematic.infoaccepts it before or after its argument.
Set it once with environment variables
Section titled “Set it once with environment variables”Tools that spawn builder inside a checkout — scripts, editors, agents — can set
BUILDER_MANIFEST and BUILDER_SDK_ROOT instead of passing the flags. Set once, they let
every checkout run the shared schematics with plain commands:
export BUILDER_MANIFEST=~/work/app-schematicsexport BUILDER_SDK_ROOT=~/.bun/install/global/node_modules/@pbuilder/sdkbuilder execute default:hello --name=orders- Each flag always wins over its variable. A flag that names an unusable directory fails on its own; it never falls back to the variable.
- When
BUILDER_MANIFESTpoints somewhere other than the working directory, every run prints thewarn_manifest_root_ambientwarning with the resolved path, so an ambient setting never goes unnoticed. A root fromBUILDER_SDK_ROOTprintswarn_sdk_root_ambient, which names the variable but not the path — check the variable itself to see which SDK ran. - The shell expands
~in theexportlines above. A settings file for an editor or agent does not, so write full paths there. - Stopped using
BUILDER_SDK_ROOT? The link it left in each checkout makes the next run fail withexecute_manifest_path_escape. See--sdk-rootandBUILDER_SDK_ROOT.
Trust rules
Section titled “Trust rules”The manifest root decides which code runs, so the CLI only accepts one that nobody else can modify. It refuses:
- a root that is writable by its group or by other users;
- a root with a directory above it that is group-writable, or world-writable without the sticky bit;
- a root, or a directory above it, owned by a user other than you or root;
- the filesystem root, and your home directory itself (a directory inside your home is fine).
On macOS and many Linux systems /tmp is writable by its group, so a manifest root under
/tmp is refused. Use a directory inside your home directory.
What stays in the workspace
Section titled “What stays in the workspace”-
builder new schematicalways writes to the working directory. If--manifestorBUILDER_MANIFESTnames a different directory, it refuses withmanifest_scoped_authoring_refusedbefore writing anything. To author in the shared directory,cdinto it and runbuilder new schematicthere. The shared directory has no SDK, andnew schematicdoes not readBUILDER_SDK_ROOT, so the scaffold skipsschema.generated.tswith a warning. Regenerate types with the SDK root’s own script — codegen writes inside the project you name:Terminal window node $BUILDER_SDK_ROOT/dist/bin/pbuilder-codegen.js --project ~/work/app-schematics -
SDK settings in the external manifest are ignored. An
sdk.rootorsdk.versionin the externalproject-builder.jsonis not used —--sdk-root,BUILDER_SDK_ROOTor the workspace’s own installed SDK supply the SDK, and the workspace’s ownpackage.jsonsets the version requirement. When the external manifest declares ansdk.root, the run warns withwarn_manifest_sdk_root_ignored. -
Other commands do not accept
--manifest: passing it toinit, for example, fails withmanifest_root_unsupported_command.
Paths inside the manifest
Section titled “Paths inside the manifest”Every path the external manifest registers must be relative to the manifest root and stay inside it:
- An absolute
pathfails withcollection_absolute_path. - A path that resolves outside the manifest root — for example
../app/schematics/hello— fails as escaping the named manifest root.
Related
Section titled “Related”builder executeandbuilder info— flag reference.- CLI output and errors — every
--manifesterror and warning. - Evaluate without adopting the SDK — use an SDK in a
checkout without editing
package.json.