1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 10:54:44 +00:00
Files
tools/.github/skills/monorepo/SKILL.md

9.4 KiB

name, description
name description
monorepo Manage the @robonen/tools monorepo. Use when: installing dependencies, creating new packages, linting, building, testing, publishing, or scaffolding workspace packages. Covers pnpm catalogs, tsdown builds, oxlint presets, vitest projects, JSR/NPM publishing.

Monorepo Management

Overview

This is a pnpm workspace monorepo (@robonen/tools) with shared configs, strict dependency management via catalogs, and automated publishing.

Workspace Layout

Directory Purpose Examples
configs/* Shared tooling configs oxlint, tsconfig, tsdown
core/* Platform-agnostic TS libraries stdlib, platform, encoding
vue/* Vue 3 packages primitives, toolkit
docs Nuxt 4 documentation site
infra/* Infrastructure configs renovate

Installing Dependencies

Always use pnpm. Never use npm or yarn.

Add a dependency to a specific package

# Runtime dependency
pnpm -C <package-path> add <dep-name>

# Dev dependency
pnpm -C <package-path> add -D <dep-name>

Examples:

pnpm -C core/stdlib add -D oxlint
pnpm -C vue/primitives add vue

Use catalogs for shared versions

Versions shared across multiple packages MUST use the pnpm catalog system. The catalog is defined in pnpm-workspace.yaml:

catalog:
  vitest: ^4.0.18
  tsdown: ^0.21.0
  oxlint: ^1.2.0
  vue: ^3.5.28
  # ... etc

In package.json, reference catalog versions with the catalog: protocol:

{
  "devDependencies": {
    "vitest": "catalog:",
    "oxlint": "catalog:"
  }
}

When to add to catalog: If the dependency is used in 2+ packages, add it to pnpm-workspace.yaml under catalog: and use catalog: in each package.json.

When NOT to use catalog: Package-specific dependencies used in only one package (e.g., citty in root).

Internal workspace dependencies

Reference sibling packages with the workspace protocol:

{
  "devDependencies": {
    "@robonen/oxlint": "workspace:*",
    "@robonen/tsconfig": "workspace:*",
    "@robonen/tsdown": "workspace:*"
  }
}

Nearly every package depends on these three shared config packages.

After installing

Always run pnpm install at the root after editing pnpm-workspace.yaml or any package.json manually.

Creating a New Package

Note: The existing bin/cli.ts (pnpm create) is outdated — it generates Vite configs instead of tsdown and lacks oxlint/vitest setup. Follow the manual steps below instead.

1. Create the directory

Choose the correct parent based on package type:

  • core/<name> — Platform-agnostic TypeScript library
  • vue/<name> — Vue 3 library (needs jsdom, vue deps)
  • configs/<name> — Shared configuration package

2. Create package.json

{
  "name": "@robonen/<name>",
  "version": "0.0.1",
  "license": "Apache-2.0",
  "description": "",
  "packageManager": "pnpm@10.29.3",
  "engines": { "node": ">=24.13.1" },
  "type": "module",
  "files": ["dist"],
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "scripts": {
    "lint:check": "oxlint -c oxlint.config.ts",
    "lint:fix": "oxlint -c oxlint.config.ts --fix",
    "test": "vitest run",
    "dev": "vitest dev",
    "build": "tsdown"
  },
  "devDependencies": {
    "@robonen/oxlint": "workspace:*",
    "@robonen/tsconfig": "workspace:*",
    "@robonen/tsdown": "workspace:*",
      "@stylistic/eslint-plugin": "catalog:",
    "oxlint": "catalog:",
    "tsdown": "catalog:"
  }
}

For Vue packages, also add:

{
  "dependencies": {
    "vue": "catalog:",
    "@vue/shared": "catalog:"
    "@stylistic/eslint-plugin": "catalog:",
  },
  "devDependencies": {
    "@vue/test-utils": "catalog:"
  }
}

For packages with sub-path exports (like core/platform):

{
  "exports": {
    "./browsers": {
      "types": "./dist/browsers.d.ts",
      "import": "./dist/browsers.js",
      "require": "./dist/browsers.cjs"
    }
  }
}

3. Create tsconfig.json

Node/core package:

{
  "extends": "@robonen/tsconfig/tsconfig.json"
}

Vue package (needs DOM types and path aliases):

{
  "extends": "@robonen/tsconfig/tsconfig.json",
  "compilerOptions": {
    "lib": ["DOM"],
    "baseUrl": ".",
    "paths": { "@/*": ["src/*"] }
  }
}

4. Create tsdown.config.ts

Standard:

import { defineConfig } from 'tsdown';
import { sharedConfig } from '@robonen/tsdown';

export default defineConfig({
  ...sharedConfig,
  entry: ['src/index.ts'],
});

Vue package (externalize vue, bundle internal deps):

import { defineConfig } from 'tsdown';
import { sharedConfig } from '@robonen/tsdown';

export default defineConfig({
  ...sharedConfig,
  entry: ['src/index.ts'],
  deps: {
    neverBundle: ['vue'],
    alwaysBundle: [/^@robonen\//, '@vue/shared'],
  },
  inputOptions: {
    resolve: {
      alias: { '@vue/shared': '@vue/shared/dist/shared.esm-bundler.js' },
    },
  },
  define: { __DEV__: 'false' },
});

5. Create oxlint.config.ts

Standard (node packages):

import { defineConfig } from 'oxlint';
import { compose, base, typescript, imports, stylistic } from '@robonen/oxlint';

export default defineConfig(compose(base, typescript, imports, stylistic));

6. Create vitest.config.ts

Node package:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'node',
  },
});

Vue package:

import { defineConfig } from 'vitest/config';
import { resolve } from 'node:path';

export default defineConfig({
  define: { __DEV__: 'true' },
  resolve: {
    alias: { '@': resolve(__dirname, './src') },
  },
  test: {
    environment: 'jsdom',
  },
});

7. Create jsr.json (for publishable packages)

{
  "$schema": "https://jsr.io/schema/config-file.v1.json",
  "name": "@robonen/<name>",
  "version": "0.0.1",
  "exports": "./src/index.ts"
}

8. Create source files

mkdir -p src
touch src/index.ts

9. Register with vitest projects

Add the new vitest.config.ts path to the root vitest.config.ts projects array.

10. Install dependencies

pnpm install

11. Verify

pnpm -C <package-path> build
pnpm -C <package-path> lint
pnpm -C <package-path> test

Linting

Uses oxlint (not ESLint) with composable presets from @robonen/oxlint.

Run linting

# Check lint errors (no auto-fix)
pnpm -C <package-path> lint:check

# Auto-fix lint errors
pnpm -C <package-path> lint:fix

# Check all packages
pnpm lint:check

# Fix all packages
pnpm lint:fix

Available presets

Preset Purpose
base ESLint core + Oxc + Unicorn rules
typescript TypeScript rules (via overrides on *.ts files)
imports Import ordering, cycles, duplicates
stylistic Code style via @stylistic/eslint-plugin
vue Vue 3 Composition API rules
vitest Test file rules
node Node.js-specific rules

Compose presets in oxlint.config.ts:

import { defineConfig } from 'oxlint';
import { compose, base, typescript, imports } from '@robonen/oxlint';

export default defineConfig(compose(base, typescript, imports));

**Recommended:** Include `stylistic` preset for code formatting:
```typescript
import { defineConfig } from 'oxlint';
import { compose, base, typescript, imports, stylistic } from '@robonen/oxlint';

export default defineConfig(compose(base, typescript, imports, stylistic));

When using stylistic, add @stylistic/eslint-plugin to devDependencies:

{
  "devDependencies": {
    "@stylistic/eslint-plugin": "catalog:"
  }
}

## Building

```bash
# Build a specific package
pnpm -C <package-path> build

# Build all packages
pnpm build

All packages use tsdown with shared config from @robonen/tsdown. Output: ESM (.js/.mjs) + CJS (.cjs) + type declarations (.d.ts). Every bundle includes an Apache-2.0 license banner.

Testing

# Run tests in a specific package
pnpm -C <package-path> test

# Run all tests (via vitest projects)
pnpm test

# Interactive test UI
pnpm test:ui

# Watch mode in a package
pnpm -C <package-path> dev

Uses vitest with project-based configuration. Root vitest.config.ts lists all package vitest configs as projects.

Publishing

Publishing is automated via GitHub Actions on push to master:

  1. CI builds and tests all packages
  2. Publish workflow compares each package's version in package.json against npm registry
  3. If version changed → pnpm publish --access public

To publish: Bump the version in package.json (and jsr.json if present), then merge to master.

NPM scope: All packages publish under @robonen/.

Documentation

pnpm docs:dev      # Start Nuxt dev server
pnpm docs:generate  # Generate static site
pnpm docs:preview   # Preview generated site
pnpm docs:extract   # Extract API docs

Key Conventions

  • ESM-first: All packages use "type": "module"
  • Strict TypeScript: strict: true, noUncheckedIndexedAccess: true, verbatimModuleSyntax: true
  • License: Apache-2.0 for all published packages
  • Node version: ≥24.13.1 (set in engines and CI)
  • pnpm version: Pinned in packageManager field
  • No barrel re-exports of entire modules — export explicitly
  • __DEV__ global: false in builds, true in tests (Vue packages only)