pnpm-templates-sync
v0.1.0
Published
Readme
pnpm-templates-sync
Synchronize pnpm workspace dependencies inside package templates before running a command, then automatically restore the original template dependencies afterward.
It is useful for monorepos that contain scaffolding/templates whose package.json files use pnpm-specific protocols, such as:
catalog:workspace:*workspace:^workspace:~
When publishing or otherwise processing those templates, pnpm-templates-sync temporarily converts these protocols into publishable dependency versions.
Installation
pnpm add -D pnpm-templates-syncCLI
The simplest way to use the package is as a command wrapper.
node scripts/sync.js pnpm publish -rThe command is executed in three steps:
prepare templates
↓
run command
↓
restore templatesFor example:
node scripts/sync.js pnpm publish -ris equivalent to:
1. Resolve catalog/workspace dependencies
2. Temporarily update template package.json files
3. Run `pnpm publish -r`
4. Restore the original dependency specificationsUsing a package script
Add a script to the root package.json:
{
"scripts": {
"sync": "node scripts/sync.js"
}
}Then:
pnpm sync pnpm publish -rYou can use the same wrapper for other commands:
pnpm sync pnpm buildpnpm sync pnpm packpnpm sync pnpm publish -rThe command after sync is passed directly to the wrapper.
Why?
Suppose a template contains:
{
"dependencies": {
"ziko": "workspace:*",
"zod": "catalog:"
}
}These references are useful inside the monorepo, but they are not the dependency specifications you necessarily want to publish with the template.
Before running the command, the template can temporarily become:
{
"dependencies": {
"ziko": "^2.0.0",
"zod": "^4.0.0"
}
}After the command finishes, the original values are restored:
{
"dependencies": {
"ziko": "workspace:*",
"zod": "catalog:"
}
}The template source therefore remains unchanged.
JavaScript API
The package also exposes the synchronization API directly.
import {
createTemplateSync,
} from "pnpm-templates-sync";Create a synchronizer:
const sync = createTemplateSync(
root,
[
"create-ziko/templates/*",
]
);createTemplateSync(root, templates)
Creates a template synchronization instance.
Parameters
root
The root directory of the pnpm workspace.
const root = path.resolve(
import.meta.dirname,
".."
);templates
An array of template paths relative to the packages directory.
[
"create-ziko/templates/*",
"create-ufbr/templates/*",
]Wildcard paths are supported.
sync.prepare()
Resolves pnpm-specific dependency protocols in template package.json files.
For example:
{
"dependencies": {
"ziko": "workspace:*",
"zod": "catalog:"
}
}can become:
{
"dependencies": {
"ziko": "^2.0.0",
"zod": "^4.0.0"
}
}The original files are not permanently changed because sync.restore() can reconstruct the original specifications.
sync.restore()
Restores the pnpm dependency protocols.
For example:
{
"dependencies": {
"ziko": "^2.0.0",
"zod": "^4.0.0"
}
}is restored to:
{
"dependencies": {
"ziko": "workspace:*",
"zod": "catalog:"
}
}Recommended usage
When calling prepare() manually, use try/finally:
const sync = createTemplateSync(
root,
templates
);
sync.prepare();
try {
// Your command
} finally {
sync.restore();
}This ensures that the templates are restored even when the command fails.
Supported pnpm protocols
Catalog
A dependency using:
{
"dependencies": {
"zod": "catalog:"
}
}is resolved from the root pnpm-workspace.yaml:
catalog:
zod: ^4.0.0Result:
{
"dependencies": {
"zod": "^4.0.0"
}
}Workspace protocol
The following workspace protocols are supported:
workspace:*
workspace:^
workspace:~For example:
{
"dependencies": {
"ziko": "workspace:*"
}
}If the workspace package version is:
{
"name": "ziko",
"version": "2.0.0"
}the prepared dependency becomes:
{
"dependencies": {
"ziko": "^2.0.0"
}
}Similarly:
workspace:^ → ^2.0.0
workspace:~ → ~2.0.0Normal versions
Normal dependency specifications are left unchanged:
{
"dependencies": {
"react": "^19.0.0"
}
}remains:
{
"dependencies": {
"react": "^19.0.0"
}
}Template configuration
Templates are specified relative to the workspace's packages directory.
For example:
packages/
├── create-ziko/
│ └── templates/
│ ├── spa-js/
│ │ └── package.json
│ └── fbr-js/
│ └── package.json
└── ...Configuration:
const templates = [
"create-ziko/templates/*",
];will discover:
packages/create-ziko/templates/spa-js
packages/create-ziko/templates/fbr-jsYou can configure multiple template groups:
const templates = [
"create-ziko/templates/*",
"create-ufbr/templates/*",
];Example
A typical repository structure:
repo/
├── package.json
├── pnpm-workspace.yaml
├── packages/
│ └── create-ziko/
│ └── templates/
│ ├── spa-js/
│ │ └── package.json
│ └── fbr-js/
│ └── package.json
└── scripts/
└── sync.jsscripts/sync.js:
import path from "node:path";
import { spawn } from "node:child_process";
import {
createTemplateSync,
} from "pnpm-templates-sync";
const root = path.resolve(
import.meta.dirname,
".."
);
const SCAFFOLDERS = [
"create-ziko/templates/*",
];
const sync = createTemplateSync(
root,
SCAFFOLDERS
);
const [command, ...args] =
process.argv.slice(2);
if (!command) {
console.error(
"Usage: node scripts/sync.js <command> [...args]"
);
process.exit(1);
}
sync.prepare();
const child = spawn(
command,
args,
{
cwd: root,
stdio: "inherit",
shell: true,
}
);
child.on("close", code => {
try {
sync.restore();
} finally {
process.exit(code ?? 1);
}
});
child.on("error", error => {
console.error(error);
try {
sync.restore();
} finally {
process.exit(1);
}
});Then:
node scripts/sync.js pnpm publish -rDesign
pnpm-templates-sync deliberately does not permanently modify templates.
Its workflow is:
┌──────────────┐
│ Templates │
└──────┬───────┘
│
prepare()
│
▼
┌─────────────────────┐
│ Publishable versions│
└──────────┬──────────┘
│
run command
│
▼
restore()
│
▼
┌──────────────┐
│ Templates │
│ restored │
└──────────────┘This makes it particularly useful for monorepos where templates are maintained alongside the packages they depend on.
Requirements
- Node.js
- pnpm workspace
- Templates containing
package.jsonfiles - A
pnpm-workspace.yamlat the workspace root
License
MIT
