$ du -sh node_modules # content store at root
1.2G
$ du -sh packages/*/node_modules # per-package symlink farms
4.8M packages/didof.dev/node_modules
3.2M packages/velocaption.com/node_modules
3.1M packages/speechstudio.ai/node_modules
2.9M packages/linkpreview.ai/node_modules
$ du -sh node_modules # content store at root
1.2G
$ du -sh packages/*/node_modules # per-package symlink farms
4.8M packages/didof.dev/node_modules
3.2M packages/velocaption.com/node_modules
3.1M packages/speechstudio.ai/node_modules
2.9M packages/linkpreview.ai/node_modules
$ du -sh node_modules # content store at root
1.2G
$ du -sh packages/*/node_modules # per-package symlink farms
4.8M packages/didof.dev/node_modules
3.2M packages/velocaption.com/node_modules
3.1M packages/speechstudio.ai/node_modules
2.9M packages/linkpreview.ai/node_modules
import debounce from "lodash/debounce";
import debounce from "lodash/debounce";
import debounce from "lodash/debounce";
Could not resolve "lodash/debounce" from src/hooks/useDebounce.ts
Could not resolve "lodash/debounce" from src/hooks/useDebounce.ts
Could not resolve "lodash/debounce" from src/hooks/useDebounce.ts
[plugin:vite:import-analysis] Failed to resolve import "lodash/debounce" from "src/hooks/useDebounce.ts"
[plugin:vite:import-analysis] Failed to resolve import "lodash/debounce" from "src/hooks/useDebounce.ts"
[plugin:vite:import-analysis] Failed to resolve import "lodash/debounce" from "src/hooks/useDebounce.ts"
content-hub/
├── pnpm-workspace.yaml
├── package.json # root; devDeps shared across packages
├── pnpm-lock.yaml # single lockfile for everything
├── scripts/ # monorepo-wide scripts
├── packages/
│ ├── shared/ # @didof/shared: plugins, schemas, utils
│ ├── didof.dev/
│ ├── velocaption.com/
│ ├── speechstudio.ai/
│ └── linkpreview.ai/
└── .gitignore
content-hub/
├── pnpm-workspace.yaml
├── package.json # root; devDeps shared across packages
├── pnpm-lock.yaml # single lockfile for everything
├── scripts/ # monorepo-wide scripts
├── packages/
│ ├── shared/ # @didof/shared: plugins, schemas, utils
│ ├── didof.dev/
│ ├── velocaption.com/
│ ├── speechstudio.ai/
│ └── linkpreview.ai/
└── .gitignore
content-hub/
├── pnpm-workspace.yaml
├── package.json # root; devDeps shared across packages
├── pnpm-lock.yaml # single lockfile for everything
├── scripts/ # monorepo-wide scripts
├── packages/
│ ├── shared/ # @didof/shared: plugins, schemas, utils
│ ├── didof.dev/
│ ├── velocaption.com/
│ ├── speechstudio.ai/
│ └── linkpreview.ai/
└── .gitignore
{ "scripts": { "dev:didof": "pnpm --filter didof.dev dev", "dev:velocaption": "pnpm --filter velocaption.com dev", "build:all": "pnpm -r build", "sync": "pnpm tsx scripts/content-sync.ts", "ship:didof": "pnpm --filter didof.dev ship", "ship:velocaption": "pnpm --filter velocaption.com ship" }
}
{ "scripts": { "dev:didof": "pnpm --filter didof.dev dev", "dev:velocaption": "pnpm --filter velocaption.com dev", "build:all": "pnpm -r build", "sync": "pnpm tsx scripts/content-sync.ts", "ship:didof": "pnpm --filter didof.dev ship", "ship:velocaption": "pnpm --filter velocaption.com ship" }
}
{ "scripts": { "dev:didof": "pnpm --filter didof.dev dev", "dev:velocaption": "pnpm --filter velocaption.com dev", "build:all": "pnpm -r build", "sync": "pnpm tsx scripts/content-sync.ts", "ship:didof": "pnpm --filter didof.dev ship", "ship:velocaption": "pnpm --filter velocaption.com ship" }
}
packages: - "packages/*"
packages: - "packages/*"
packages: - "packages/*"
{ "name": "@didof/shared", "version": "0.0.1"
}
{ "name": "@didof/shared", "version": "0.0.1"
}
{ "name": "@didof/shared", "version": "0.0.1"
}
{ "name": "didof.dev", "dependencies": { "@didof/shared": "workspace:*" }
}
{ "name": "didof.dev", "dependencies": { "@didof/shared": "workspace:*" }
}
{ "name": "didof.dev", "dependencies": { "@didof/shared": "workspace:*" }
}
{ "name": "@didof/shared", "exports": { ".": "./index.ts", "./plugins": "./plugins/index.ts", "./schemas": "./schemas/index.ts", "./astro-config-base": "./astro-config-base.ts", "./components/CodeBlockScript.astro": "./components/CodeBlockScript.astro", "./styles/codeblock.css": "./styles/codeblock.css" }
}
{ "name": "@didof/shared", "exports": { ".": "./index.ts", "./plugins": "./plugins/index.ts", "./schemas": "./schemas/index.ts", "./astro-config-base": "./astro-config-base.ts", "./components/CodeBlockScript.astro": "./components/CodeBlockScript.astro", "./styles/codeblock.css": "./styles/codeblock.css" }
}
{ "name": "@didof/shared", "exports": { ".": "./index.ts", "./plugins": "./plugins/index.ts", "./schemas": "./schemas/index.ts", "./astro-config-base": "./astro-config-base.ts", "./components/CodeBlockScript.astro": "./components/CodeBlockScript.astro", "./styles/codeblock.css": "./styles/codeblock.css" }
}
import { baseBlogSchema } from "@didof/shared/schemas";
import { codeblockCopyTransformer } from "@didof/shared/plugins";
import "@didof/shared/styles/codeblock.css";
import { baseBlogSchema } from "@didof/shared/schemas";
import { codeblockCopyTransformer } from "@didof/shared/plugins";
import "@didof/shared/styles/codeblock.css";
import { baseBlogSchema } from "@didof/shared/schemas";
import { codeblockCopyTransformer } from "@didof/shared/plugins";
import "@didof/shared/styles/codeblock.css";
mkdir content-hub && cd content-hub
pnpm init
-weight: 500;">git init
mkdir content-hub && cd content-hub
pnpm init
-weight: 500;">git init
mkdir content-hub && cd content-hub
pnpm init
-weight: 500;">git init
cat > pnpm-workspace.yaml <<'EOF'
packages: - "packages/*"
EOF
cat > pnpm-workspace.yaml <<'EOF'
packages: - "packages/*"
EOF
cat > pnpm-workspace.yaml <<'EOF'
packages: - "packages/*"
EOF
mkdir packages
mv ../didof.dev packages/
mv ../velocaption.com packages/
# ... one mv per project
mkdir packages
mv ../didof.dev packages/
mv ../velocaption.com packages/
# ... one mv per project
mkdir packages
mv ../didof.dev packages/
mv ../velocaption.com packages/
# ... one mv per project
rm -rf packages/*/node_modules packages/*/pnpm-lock.yaml
rm -rf packages/*/node_modules packages/*/pnpm-lock.yaml
rm -rf packages/*/node_modules packages/*/pnpm-lock.yaml
pnpm -weight: 500;">install
pnpm -weight: 500;">install
pnpm -weight: 500;">install
pnpm -r ls --depth -1
pnpm -r ls --depth -1
pnpm -r ls --depth -1
pnpm add -D -w typescript tsx sharp
pnpm add -D -w typescript tsx sharp
pnpm add -D -w typescript tsx sharp
pnpm --filter didof.dev dev
pnpm --filter didof.dev dev
pnpm --filter didof.dev dev
pnpm -r build
pnpm -r build
pnpm -r build
pnpm --filter "./packages/*" check
pnpm --filter "./packages/*" check
pnpm --filter "./packages/*" check
pnpm --filter didof.dev add @radix-ui/react-dialog
pnpm --filter didof.dev add @radix-ui/react-dialog
pnpm --filter didof.dev add @radix-ui/react-dialog
pnpm add -D -w sharp
pnpm add -D -w sharp
pnpm add -D -w sharp
{ "name": "velocaption.com", "exports": { ".": "./...", "./tools-registry": "./src/features/tools/registry.ts" }
}
{ "name": "velocaption.com", "exports": { ".": "./...", "./tools-registry": "./src/features/tools/registry.ts" }
}
{ "name": "velocaption.com", "exports": { ".": "./...", "./tools-registry": "./src/features/tools/registry.ts" }
}
{ "dependencies": { "velocaption.com": "workspace:*" }
}
{ "dependencies": { "velocaption.com": "workspace:*" }
}
{ "dependencies": { "velocaption.com": "workspace:*" }
}
import { TOOL_REGISTRY } from "velocaption.com/tools-registry";
import { TOOL_REGISTRY } from "velocaption.com/tools-registry";
import { TOOL_REGISTRY } from "velocaption.com/tools-registry";
{ "scripts": { "deploy": "wrangler pages deploy dist --project-name didof-dev", "ship": "pnpm check && pnpm build && pnpm run deploy" }
}
{ "scripts": { "deploy": "wrangler pages deploy dist --project-name didof-dev", "ship": "pnpm check && pnpm build && pnpm run deploy" }
}
{ "scripts": { "deploy": "wrangler pages deploy dist --project-name didof-dev", "ship": "pnpm check && pnpm build && pnpm run deploy" }
}
pnpm add -D -w sharp
pnpm add -D -w sharp
pnpm add -D -w sharp
pnpm --filter didof.dev add @radix-ui/react-dialog
pnpm --filter didof.dev add @radix-ui/react-dialog
pnpm --filter didof.dev add @radix-ui/react-dialog
[vite] The request url ".../node_modules/.pnpm/astro@.../dist/runtime/client/dev-toolbar/entrypoint.js" is outside of Vite serving allow list
[vite] The request url ".../node_modules/.pnpm/astro@.../dist/runtime/client/dev-toolbar/entrypoint.js" is outside of Vite serving allow list
[vite] The request url ".../node_modules/.pnpm/astro@.../dist/runtime/client/dev-toolbar/entrypoint.js" is outside of Vite serving allow list
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path"; const MONOREPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); export default defineConfig({ vite: { server: { fs: { allow: [MONOREPO_ROOT], }, }, },
});
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path"; const MONOREPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); export default defineConfig({ vite: { server: { fs: { allow: [MONOREPO_ROOT], }, }, },
});
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path"; const MONOREPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); export default defineConfig({ vite: { server: { fs: { allow: [MONOREPO_ROOT], }, }, },
});
import { resolve } from "node:path";
import { pathToFileURL } from "node:url"; const registryPath = resolve( __dirname, "../../velocaption.com/src/features/tools/registry.ts",
);
const mod = await import(pathToFileURL(registryPath).href);
const registry = mod.TOOL_REGISTRY;
import { resolve } from "node:path";
import { pathToFileURL } from "node:url"; const registryPath = resolve( __dirname, "../../velocaption.com/src/features/tools/registry.ts",
);
const mod = await import(pathToFileURL(registryPath).href);
const registry = mod.TOOL_REGISTRY;
import { resolve } from "node:path";
import { pathToFileURL } from "node:url"; const registryPath = resolve( __dirname, "../../velocaption.com/src/features/tools/registry.ts",
);
const mod = await import(pathToFileURL(registryPath).href);
const registry = mod.TOOL_REGISTRY; - pnpm-workspace.yaml is a three-line file that declares which folders are part of the workspace. That's the whole mechanism.
- workspace:* in a dependency spec points at a sibling package. No publishing, no -weight: 500;">npm link, no version drift.
- Run any script across packages with pnpm --filter <name> <script>. Combine filters for graph-aware builds.
- Subpath exports in a sibling's package.json lets another package import just one module (e.g. a registry file) without pulling in the whole site.
- The five gotchas at the end of this post cost me roughly four hours combined. All of them are cheap to fix once you know about them. - You have so many packages that topological ordering alone isn't enough and you need smart caching of build outputs across runs
- CI needs to skip rebuilds of packages whose source files haven't changed
- You want a built-in dependency graph visualizer