Skip to content

Commit f6e3aa3

Browse files
committed
fix(cli): isolated quick-mode npm install and Vitest
Quick mode writes package.json under .barodoc/ and runs npm install there instead of symlinking monorepo root node_modules. Monorepo @barodoc/* packages use npm pack with workspace-to-semver rewrite. Add project.test.ts, changelog v10.0.10, changeset, and AGENTS/DEVELOPMENT/skill updates. Fixes #163 Made-with: Cursor
1 parent a772b50 commit f6e3aa3

12 files changed

Lines changed: 654 additions & 72 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"barodoc": patch
3+
---
4+
5+
Quick mode: isolated `npm install` under `.barodoc/` (no symlink to monorepo root `node_modules`); monorepo `@barodoc/*` via `npm pack` with workspace-to-semver rewrite; dependency hash cache; file-based `astro.config.mjs`; `scheduler` for React; Vitest for `project.ts` helpers.

.cursor/skills/barodoc-cli/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ npm install -g barodoc
1717
npx barodoc serve
1818
```
1919

20+
## Quick mode vs full Astro project
21+
22+
- **Quick mode:** a folder with Markdown and `barodoc.config.json` only (no `astro.config.mjs`). The CLI uses **`<dir>/.barodoc/`** as the Astro app root, runs **`npm install`** there on first run or when the dependency set changes, then starts `astro dev` / `astro build`. You need **npm** on `PATH` and network access for that install.
23+
- **Full project:** the directory contains `astro.config.mjs` — Barodoc runs Astro against that project directly without the `.barodoc/` temp layout.
24+
2025
## Commands
2126

2227
### serve - Development Server
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
name: barodoc-dev
3+
description: Barodoc monorepo development and testing workflow. Use whenever working on the barodoc repo — building packages, running the docs site or my-docs, testing plugins, or type-checking. Prefer this skill when the user says they're developing barodoc, testing locally, debugging the docs site, or verifying plugin behavior in docs or my-docs.
4+
---
5+
6+
# Barodoc Development & Testing
7+
8+
This skill covers how to build, run, and verify the Barodoc monorepo and plugins locally. For CLI usage in end-user projects, see **barodoc-cli**. For config and plugins, see **barodoc-config** and **barodoc-plugins**.
9+
10+
## Prerequisites (run first)
11+
12+
From the **barodoc** repo root:
13+
14+
```bash
15+
pnpm install
16+
pnpm build:packages
17+
```
18+
19+
`build:packages` builds core, theme-docs, barodoc CLI, and all plugins. Run it after changing any package, then test.
20+
21+
---
22+
23+
## 1. Testing with the docs site
24+
25+
The **docs** workspace is a full Astro project. Use it to test the framework and plugins in custom mode.
26+
27+
| Goal | Command |
28+
|------|---------|
29+
| Dev server | `pnpm dev` (port 4321 or next available) |
30+
| Production build | `pnpm build` (output in `docs/dist`) |
31+
| Build + preview (full behavior) | `pnpm build:packages && pnpm build && pnpm preview` |
32+
33+
**Why build + preview:** In the monorepo, `pnpm dev` can fail to load theme-docs React islands (e.g. mobile menu) due to dev-only `/@fs/` handling. For full navigation and mobile menu, use build then preview.
34+
35+
**Config:** `docs/barodoc.config.json`. To test a plugin, add it to the `plugins` array.
36+
37+
**Type-check (all packages):** Run after `astro sync` so content types exist:
38+
39+
```bash
40+
pnpm --filter docs exec astro sync
41+
pnpm -r exec tsc --noEmit
42+
```
43+
44+
---
45+
46+
## 2. Testing with my-docs (quick mode)
47+
48+
**my-docs** is a separate project (e.g. sibling `barocss/my-docs`) with only Markdown and `barodoc.config.json`. The CLI runs in quick mode via a project under `my-docs/.barodoc/`: it runs **`npm install`** there (isolated `package.json`, not a symlink to the monorepo root `node_modules`). From the Barodoc repo, `@barodoc/*` deps are installed via **`npm pack`** tarballs with `workspace:*` rewritten to semver. First run needs **npm** on `PATH` and network; see **AGENTS.md** for details.
49+
50+
### From the barodoc repo
51+
52+
```bash
53+
cd barodoc
54+
pnpm build:packages
55+
pnpm barodoc serve ../my-docs
56+
pnpm barodoc build ../my-docs -o ../my-docs/dist
57+
pnpm barodoc preview ../my-docs # optional: preview production build
58+
```
59+
60+
### From the my-docs directory (no install in my-docs)
61+
62+
```bash
63+
cd my-docs
64+
node ../barodoc/packages/barodoc/dist/cli.js serve
65+
node ../barodoc/packages/barodoc/dist/cli.js build . -o dist
66+
```
67+
68+
**Plugin in my-docs:** Add to `my-docs/barodoc.config.json``plugins`. Serve/build from barodoc CLI; extra `@barodoc/*` plugins are merged into the temp `package.json` and resolved like other workspace packages when running from the monorepo.
69+
70+
**Quick-mode unit tests:** `pnpm test` runs Vitest including `packages/barodoc/src/runtime/project.test.ts` (dependency manifest, monorepo root detection, `generateAstroConfigFile` snippet).
71+
72+
---
73+
74+
## 3. Plugin development
75+
76+
- **Location:** `packages/plugin-<name>/` (e.g. `plugin-raw-md`, `plugin-rss`).
77+
- **Build:** `pnpm --filter @barodoc/plugin-<name> build` then `pnpm --filter barodoc build`.
78+
79+
**Test in docs:** Add plugin to `docs/barodoc.config.json`, run `pnpm dev` (or build+preview) and verify.
80+
81+
**Test in my-docs:** Add plugin to `my-docs/barodoc.config.json`, run `pnpm barodoc serve ../my-docs` and `pnpm barodoc build ../my-docs -o ../my-docs/dist`; confirm dev and build both work.
82+
83+
**Asset content (PDF, RST, etc.):** Put files in the docs content tree (e.g. `docs/src/content/docs/en/`), add slug to `barodoc.config.json` → navigation `pages`, then open the asset URL (e.g. `/docs/guide` for `guide.pdf`).
84+
85+
---
86+
87+
## Quick reference
88+
89+
| Goal | Command / location |
90+
|------|--------------------|
91+
| Build all packages | `pnpm build:packages` |
92+
| Dev server (docs) | `pnpm dev` |
93+
| Full behavior (docs) | `pnpm build:packages && pnpm build && pnpm preview` |
94+
| Serve my-docs from repo | `pnpm barodoc serve ../my-docs` |
95+
| Build my-docs from repo | `pnpm barodoc build ../my-docs -o ../my-docs/dist` |
96+
| Type-check | `pnpm --filter docs exec astro sync` then `pnpm -r exec tsc --noEmit` |
97+
| Unit tests (incl. quick mode manifest) | `pnpm test` |
98+
| Plugin config (docs) | `docs/barodoc.config.json``plugins` |
99+
| Plugin config (my-docs) | `my-docs/barodoc.config.json``plugins` |
100+
101+
For more detail (asset viewers, deploy, ECC setup), see **DEVELOPMENT.md** in the repo.

AGENTS.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ Barodoc supports two modes:
88

99
### Quick Mode (Zero Config)
1010
- Users only need MD files and optional `barodoc.config.json`
11-
- CLI creates temporary Astro project on-the-fly
12-
- No `package.json` or `node_modules` needed
11+
- CLI creates a temporary Astro project under **`<project>/.barodoc/`** and runs Astro from there
12+
- **Isolated install:** quick mode writes a dedicated `package.json` in `.barodoc/` and runs **`npm install`** there (not a symlink to the CLI or monorepo root `node_modules`), so dependency resolution matches a normal project and avoids hoisted dev-tooling conflicts
13+
- **Monorepo dev:** when the CLI runs from the Barodoc repo, `@barodoc/*` packages are installed via **`npm pack`** tarballs (with `workspace:*` rewritten to semver inside the pack) so Astro 5 builds stay inside `.barodoc/` without broken path joins
14+
- **Cache:** `.barodoc-deps-hash` plus workspace package versions decide when to reinstall; `src/`, `public`, and `overrides` are refreshed each run
15+
- **Requirements:** **npm** (on `PATH`) and network on first install or after dependency changes; local npm caches live under `.barodoc/.npm-cache` and `.barodoc/.npm-pack-cache`
1316

1417
### Full Custom Mode
1518
- Users have full Astro project with `astro.config.mjs`
@@ -182,6 +185,7 @@ export default definePlugin<Options>((options) => ({
182185
| `pnpm build:packages` | Build all packages |
183186
| `pnpm changeset` | Create changeset |
184187
| `pnpm release` | Publish to npm |
188+
| `pnpm test` | Vitest (includes `packages/barodoc/src/runtime/project.test.ts` for quick-mode helpers) |
185189

186190
## Development and testing
187191

DEVELOPMENT.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ The public docs site (e.g. [barodoc.barocss.com](https://barodoc.barocss.com)) i
6969

7070
## 2. Testing with my-docs (quick mode)
7171

72-
**my-docs** is a separate project (e.g. sibling of barodoc: `barocss/my-docs`) that has only Markdown and `barodoc.config.json` (no `package.json` or Astro setup). The Barodoc CLI runs in “quick mode”: it creates a temporary project under `my-docs/.barodoc/` and runs Astro from there.
72+
**my-docs** is a separate project (e.g. sibling of barodoc: `barocss/my-docs`) that has only Markdown and `barodoc.config.json` (no `package.json` or Astro setup). The Barodoc CLI runs in “quick mode”: it maintains a temporary project under `my-docs/.barodoc/` and runs Astro from there.
73+
74+
### How `.barodoc/` dependencies work
75+
76+
- Quick mode writes **`package.json`** inside `.barodoc/` and runs **`npm install`** there (with `--legacy-peer-deps` and a project-local npm cache). It does **not** symlink the monorepo root `node_modules` into `.barodoc/`, so installs stay isolated from unrelated hoisted tooling.
77+
- When you run the CLI **from the Barodoc monorepo**, `@barodoc/*` packages are packed with **`npm pack`** into `.barodoc/.barodoc-packs/`; staged `package.json` files rewrite `workspace:*` to semver so `npm install` succeeds. First run (or after dependency / version changes) needs **network** and **npm** on `PATH`.
78+
- **`.barodoc-deps-hash`** tracks when to reuse `node_modules`; `src/`, `public`, and `overrides` are regenerated each serve/build. Users can add `.barodoc/` to `.gitignore` (Barodoc scaffolds often do).
7379

7480
### Prerequisites
7581

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
version: "10.0.10"
3+
date: 2026-03-26
4+
title: "Quick mode isolated install and Astro config"
5+
---
6+
7+
### CLI (`barodoc`)
8+
9+
- **Quick mode**`.barodoc/` now uses its own `package.json` and **`npm install`** (not a symlink to the monorepo root `node_modules`), with a dependency hash cache and project-local npm caches.
10+
- **Monorepo**`@barodoc/*` workspace packages are installed via **`npm pack`** tarballs; staged manifests rewrite `workspace:*` to semver so installs succeed.
11+
- **Astro** — quick mode continues to use a generated `astro.config.mjs` (Vite options aligned with the docs app); `scheduler` remains a direct dependency for the React runtime chain.
12+
13+
### Contributors
14+
15+
- Vitest coverage for quick-mode dependency resolution in `packages/barodoc/src/runtime/project.test.ts`.

packages/barodoc/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"picocolors": "^1.1.1",
4747
"react": "^19.0.0",
4848
"react-dom": "^19.0.0",
49+
"scheduler": "^0.27.0",
4950
"rst-compiler": "^0.5.7",
5051
"tailwindcss": "^4.0.0",
5152
"zod": "^3.24.0",

packages/barodoc/src/commands/build.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import pc from "picocolors";
33
import fs from "fs-extra";
44
import { execa } from "execa";
55
import { build as astroBuild } from "astro";
6-
import barodoc from "@barodoc/core";
7-
import docsTheme from "@barodoc/theme-docs/theme";
86
import {
97
isCustomProject,
108
loadProjectConfig,
@@ -57,26 +55,11 @@ export async function build(dir: string, options: BuildOptions): Promise<void> {
5755
try {
5856
console.log(pc.dim("Building site..."));
5957

58+
// Load integrations and Vite options from generated astro.config.mjs (same as full
59+
// projects). Programmatic inline config breaks Astro 5 static generation (astro: URLs in workers).
6060
await astroBuild({
6161
root: projectDir,
62-
configFile: false,
63-
integrations: [
64-
barodoc({
65-
config: "./barodoc.config.json",
66-
theme: docsTheme(),
67-
}),
68-
],
69-
vite: {
70-
resolve: {
71-
preserveSymlinks: true,
72-
},
73-
ssr: {
74-
noExternal: true,
75-
},
76-
},
7762
logLevel: "info",
78-
...(config.site ? { site: config.site } : {}),
79-
...(config.base ? { base: config.base } : {}),
8063
});
8164

8265
console.log();

packages/barodoc/src/commands/serve.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import path from "path";
22
import pc from "picocolors";
33
import { dev } from "astro";
4-
import barodoc from "@barodoc/core";
5-
import docsTheme from "@barodoc/theme-docs/theme";
64
import {
75
isCustomProject,
86
loadProjectConfig,
@@ -59,28 +57,11 @@ export async function serve(dir: string, options: ServeOptions): Promise<void> {
5957

6058
const devServer = await dev({
6159
root: projectDir,
62-
configFile: false,
63-
integrations: [
64-
barodoc({
65-
config: "./barodoc.config.json",
66-
theme: docsTheme(),
67-
}),
68-
],
69-
vite: {
70-
resolve: {
71-
preserveSymlinks: true,
72-
},
73-
ssr: {
74-
noExternal: [/^@barodoc\//],
75-
},
76-
},
7760
server: {
7861
port: options.port,
7962
host: options.host ? true : undefined,
8063
open: options.open,
8164
},
82-
...(config.site ? { site: config.site } : {}),
83-
...(config.base ? { base: config.base } : {}),
8465
});
8566

8667
process.on("SIGINT", async () => {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { mkdtempSync, rmSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import path from "path";
4+
import { fileURLToPath } from "url";
5+
import fs from "fs-extra";
6+
import { describe, expect, it } from "vitest";
7+
import {
8+
findMonorepoRoot,
9+
findDocsDir,
10+
generateAstroConfigFile,
11+
getDefaultConfig,
12+
hashQuickModeDeps,
13+
isCustomProject,
14+
resolveTempPackageJsonSync,
15+
} from "./project.js";
16+
17+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
18+
/** `packages/barodoc` (runtime/ is one level below package root). */
19+
const barodocPkgRoot = path.join(__dirname, "..", "..");
20+
21+
describe("quick mode package manifest", () => {
22+
it("findMonorepoRoot detects barodoc repo when run from packages/barodoc", () => {
23+
const root = findMonorepoRoot(barodocPkgRoot);
24+
expect(root).not.toBeNull();
25+
expect(fs.existsSync(path.join(root!, "pnpm-workspace.yaml"))).toBe(true);
26+
expect(
27+
fs.existsSync(path.join(root!, "packages", "barodoc", "package.json"))
28+
).toBe(true);
29+
});
30+
31+
it("findMonorepoRoot returns null outside a monorepo layout", () => {
32+
expect(findMonorepoRoot("/tmp")).toBeNull();
33+
});
34+
35+
it("resolveTempPackageJsonSync omits CLI-only deps and includes astro", () => {
36+
const myDocs = mkdtempSync(path.join(tmpdir(), "barodoc-manifest-"));
37+
const projectDir = path.join(myDocs, ".barodoc");
38+
fs.mkdirSync(projectDir, { recursive: true });
39+
try {
40+
const monorepoRoot = findMonorepoRoot(barodocPkgRoot);
41+
const pkg = resolveTempPackageJsonSync(
42+
projectDir,
43+
getDefaultConfig(),
44+
barodocPkgRoot,
45+
monorepoRoot
46+
);
47+
expect(pkg.dependencies).not.toHaveProperty("cac");
48+
expect(pkg.dependencies).not.toHaveProperty("execa");
49+
expect(pkg.dependencies).not.toHaveProperty("chokidar");
50+
expect(pkg.dependencies).not.toHaveProperty("fs-extra");
51+
expect(pkg.dependencies).not.toHaveProperty("picocolors");
52+
expect(pkg.dependencies.astro).toBeDefined();
53+
expect(pkg.dependencies["@barodoc/core"]).toBeDefined();
54+
} finally {
55+
rmSync(myDocs, { recursive: true, force: true });
56+
}
57+
});
58+
59+
it("resolveTempPackageJsonSync adds config plugins not in the CLI package.json", () => {
60+
const myDocs = mkdtempSync(path.join(tmpdir(), "barodoc-plugins-"));
61+
const projectDir = path.join(myDocs, ".barodoc");
62+
fs.mkdirSync(projectDir, { recursive: true });
63+
try {
64+
const monorepoRoot = findMonorepoRoot(barodocPkgRoot);
65+
const pkg = resolveTempPackageJsonSync(
66+
projectDir,
67+
{
68+
...getDefaultConfig(),
69+
plugins: ["@barodoc/plugin-raw-md"],
70+
},
71+
barodocPkgRoot,
72+
monorepoRoot
73+
);
74+
expect(pkg.dependencies["@barodoc/plugin-raw-md"]).toBeDefined();
75+
} finally {
76+
rmSync(myDocs, { recursive: true, force: true });
77+
}
78+
});
79+
80+
it("hashQuickModeDeps is stable and 64-char hex for the same inputs", () => {
81+
const myDocs = mkdtempSync(path.join(tmpdir(), "barodoc-hash-"));
82+
const projectDir = path.join(myDocs, ".barodoc");
83+
fs.mkdirSync(projectDir, { recursive: true });
84+
try {
85+
const monorepoRoot = findMonorepoRoot(barodocPkgRoot);
86+
const pkg = resolveTempPackageJsonSync(
87+
projectDir,
88+
getDefaultConfig(),
89+
barodocPkgRoot,
90+
monorepoRoot
91+
);
92+
const a = hashQuickModeDeps(pkg, monorepoRoot);
93+
const b = hashQuickModeDeps(pkg, monorepoRoot);
94+
expect(a).toBe(b);
95+
expect(a).toMatch(/^[a-f0-9]{64}$/);
96+
} finally {
97+
rmSync(myDocs, { recursive: true, force: true });
98+
}
99+
});
100+
});
101+
102+
describe("generateAstroConfigFile", () => {
103+
it("sets preserveSymlinks false for quick/eject template", () => {
104+
const s = generateAstroConfigFile(getDefaultConfig());
105+
expect(s).toContain("preserveSymlinks: false");
106+
});
107+
});
108+
109+
describe("findDocsDir", () => {
110+
it("prefers docs when present", () => {
111+
const d = mkdtempSync(path.join(tmpdir(), "barodoc-docsdir-"));
112+
fs.mkdirSync(path.join(d, "docs"), { recursive: true });
113+
try {
114+
expect(findDocsDir(d)).toBe("docs");
115+
} finally {
116+
rmSync(d, { recursive: true, force: true });
117+
}
118+
});
119+
});
120+
121+
describe("isCustomProject", () => {
122+
it("is false without astro.config", () => {
123+
const d = mkdtempSync(path.join(tmpdir(), "barodoc-custom-"));
124+
try {
125+
expect(isCustomProject(d)).toBe(false);
126+
} finally {
127+
rmSync(d, { recursive: true, force: true });
128+
}
129+
});
130+
});

0 commit comments

Comments
 (0)