feat: add complete monorepo structure and all packages
This completes the initial monorepo setup with all packages and configuration. Packages added: - @tpmjs/ui - React component library with .ts-only components - @tpmjs/utils - Utility functions (cn, format) - @tpmjs/types - TypeScript types and Zod schemas - @tpmjs/env - Environment variable validation - @tpmjs/test - Shared Vitest configuration - @tpmjs/mocks - MSW mock server - @tpmjs/config - Shared configurations (Biome, ESLint, Tailwind, TypeScript) Applications added: - @tpmjs/web - Next.js 16 App Router Infrastructure: - Turborepo configuration with build pipeline - pnpm workspace setup - GitHub Actions CI/CD - Changesets for versioning - Lefthook pre-commit hooks - VSCode workspace settings - Comprehensive documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bc2f4a01cf
commit
78e6b9f4b5
67 changed files with 9889 additions and 0 deletions
11
.changeset/README.md
Normal file
11
.changeset/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Changesets
|
||||
|
||||
This folder contains changeset files which describe changes to packages in this monorepo.
|
||||
|
||||
To create a new changeset, run:
|
||||
|
||||
```bash
|
||||
pnpm changeset
|
||||
```
|
||||
|
||||
Then follow the prompts to describe your changes.
|
||||
20
.changeset/config.json
Normal file
20
.changeset/config.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [
|
||||
"@tpmjs/config",
|
||||
"@tpmjs/eslint-config",
|
||||
"@tpmjs/tailwind-config",
|
||||
"@tpmjs/tsconfig",
|
||||
"@tpmjs/test",
|
||||
"@tpmjs/mocks",
|
||||
"@tpmjs/storybook",
|
||||
"@tpmjs/web"
|
||||
]
|
||||
}
|
||||
95
.github/workflows/ci.yml
vendored
Normal file
95
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Format check
|
||||
run: pnpm format:check
|
||||
|
||||
type-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Type check
|
||||
run: pnpm type-check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
47
.github/workflows/release.yml
vendored
Normal file
47
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.14.0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
cache: 'pnpm'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Create Release Pull Request or Publish
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish: pnpm changeset:publish
|
||||
version: pnpm changeset:version
|
||||
commit: 'chore: version packages'
|
||||
title: 'chore: version packages'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# ide
|
||||
.idea
|
||||
|
||||
# storybook
|
||||
storybook-static
|
||||
|
||||
# changesets
|
||||
.changeset/*.md
|
||||
!.changeset/README.md
|
||||
10
.npmrc
Normal file
10
.npmrc
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Strict installation
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
|
||||
# Performance
|
||||
prefer-frozen-lockfile=true
|
||||
shamefully-hoist=false
|
||||
|
||||
# Publishing
|
||||
access=public
|
||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.codeActionsOnSave": {
|
||||
"quickfix.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
242
CLAUDE.md
Normal file
242
CLAUDE.md
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
# TPMJS - Tool Package Manager for AI Agents
|
||||
|
||||
## Philosophy
|
||||
|
||||
TPMJS is a registry and package manager for AI agent tools. Just as npm transformed how developers share and consume JavaScript packages, TPMJS aims to do the same for the emerging ecosystem of AI agent tooling.
|
||||
|
||||
### The Problem We're Solving
|
||||
|
||||
AI agents are becoming increasingly capable, but they face a fundamental challenge: **tool discovery and selection at scale**.
|
||||
|
||||
1. **Context Window Limitations** - When an agent has access to 10+ tools, LLMs struggle to remember and correctly select from all available options. Tool schemas consume precious context tokens.
|
||||
|
||||
2. **Tool Hallucination** - Models sometimes attempt to call tools that don't exist, or use incorrect parameter schemas, leading to failed executions and poor user experiences.
|
||||
|
||||
3. **Static Tool Sets** - Most agent implementations hardcode their available tools at build time. There's no standard way to discover, add, or share tools dynamically.
|
||||
|
||||
4. **Fragmented Ecosystem** - Developers building AI agents are recreating the same tools (web search, file operations, API integrations) over and over. There's no central place to share and discover production-ready implementations.
|
||||
|
||||
### Our Vision
|
||||
|
||||
We believe AI agent development should be:
|
||||
|
||||
- **Elegant** - Simple APIs, clear conventions, minimal boilerplate
|
||||
- **Productive** - Leverage community-built tools instead of reinventing wheels
|
||||
- **Safe** - Vetted tools with clear security boundaries and permissions
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Tools
|
||||
A tool is a capability that an AI agent can invoke. Tools have:
|
||||
- A unique name/identifier
|
||||
- A description (used for semantic search and LLM understanding)
|
||||
- A parameter schema (typically defined with Zod or JSON Schema)
|
||||
- An implementation function
|
||||
|
||||
### Registry
|
||||
The registry is the central index of available tools. It enables:
|
||||
- Browsing by category
|
||||
- Semantic search (find tools by what they do, not just their name)
|
||||
- Version management
|
||||
- Usage analytics
|
||||
|
||||
### Meta-Tools
|
||||
Meta-tools are tools that help agents work with other tools. The most important is `tool-search`, which allows an agent to query the registry and load only the tools relevant to its current task. This "search-then-execute" pattern dramatically improves accuracy and token efficiency.
|
||||
|
||||
## How It Works
|
||||
|
||||
### The Search-Then-Execute Pattern
|
||||
|
||||
Instead of loading all tool schemas into context upfront (expensive and error-prone), agents using TPMJS:
|
||||
|
||||
1. **Search** - Use the `tool-search` meta-tool to find relevant tools based on the current task
|
||||
2. **Load** - Dynamically load only the matched tools into context
|
||||
3. **Execute** - Make a follow-up call with the focused tool set
|
||||
|
||||
This pattern:
|
||||
- Reduces token usage (only load what you need)
|
||||
- Improves selection accuracy (smaller choice set)
|
||||
- Eliminates hallucination (tools are confirmed to exist before use)
|
||||
- Enables runtime flexibility (tools can be added/removed without restarts)
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Compatibility
|
||||
- TypeScript-first with full type safety via Zod schemas
|
||||
- Compatible with Anthropic AI SDK, OpenAI, and other major providers
|
||||
- Minimal footprint (~340 tokens for the meta-tool)
|
||||
|
||||
### Scale
|
||||
- Supports registries with 1,000+ tools
|
||||
- Sub-2ms search latency
|
||||
- Semantic search, fuzzy matching, and category filtering
|
||||
|
||||
## Categories
|
||||
|
||||
Tools in the registry span:
|
||||
- Web & APIs
|
||||
- Databases
|
||||
- Documents
|
||||
- Images
|
||||
- Email
|
||||
- Calendar
|
||||
- Search
|
||||
- Code Execution
|
||||
- Communication
|
||||
- Analytics
|
||||
- Security
|
||||
- Workflows
|
||||
|
||||
## Development Notes
|
||||
|
||||
This project is in early development. Key areas to work on:
|
||||
|
||||
- [ ] Core registry API design
|
||||
- [ ] Tool schema specification
|
||||
- [ ] CLI for publishing and discovering tools
|
||||
- [ ] SDK integrations (Anthropic, OpenAI, etc.)
|
||||
- [ ] Search algorithm (semantic + fuzzy matching)
|
||||
- [ ] Security model and sandboxing
|
||||
- [ ] Documentation and examples
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Trust & Security** - How do we vet tools? What sandboxing is needed?
|
||||
2. **Versioning** - How do tools handle breaking changes?
|
||||
3. **Monetization** - Free tier + Pro? Marketplace cuts?
|
||||
4. **Governance** - Who decides what gets published? Moderation?
|
||||
5. **Offline/Local** - Can tools be cached locally? Private registries?
|
||||
|
||||
---
|
||||
|
||||
*"The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers."*
|
||||
|
||||
---
|
||||
|
||||
## Monorepo Setup
|
||||
|
||||
This project uses a Turborepo monorepo architecture with the following structure:
|
||||
|
||||
### Packages
|
||||
|
||||
**Published to npm (@tpmjs scope):**
|
||||
- `@tpmjs/ui` - React component library with .ts-only components
|
||||
- `@tpmjs/utils` - Utility functions (cn, format, etc.)
|
||||
- `@tpmjs/types` - Shared TypeScript types and Zod schemas
|
||||
- `@tpmjs/env` - Environment variable validation with Zod
|
||||
|
||||
**Internal tooling (private):**
|
||||
- `@tpmjs/config` - Shared configurations (Biome, ESLint, Tailwind, TypeScript)
|
||||
- `@tpmjs/eslint-config` - ESLint configuration with module boundary rules
|
||||
- `@tpmjs/tailwind-config` - Tailwind configuration with design tokens
|
||||
- `@tpmjs/tsconfig` - TypeScript configurations (base, nextjs, react-library)
|
||||
- `@tpmjs/test` - Vitest shared configuration
|
||||
- `@tpmjs/mocks` - MSW mock server for testing
|
||||
- `@tpmjs/storybook` - Component documentation and showcase
|
||||
|
||||
### Applications
|
||||
|
||||
- `@tpmjs/web` - Next.js 16 App Router application (main website)
|
||||
|
||||
### Architecture Principles
|
||||
|
||||
#### 1. .ts-only React Components
|
||||
|
||||
All UI components use `.ts` extension instead of `.tsx` and utilize `createElement`:
|
||||
|
||||
```typescript
|
||||
import { createElement, forwardRef } from 'react';
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(props, ref) => createElement('button', { ref, ...props })
|
||||
);
|
||||
```
|
||||
|
||||
**Why?**
|
||||
- Explicit runtime behavior
|
||||
- Prevents JSX spreading anti-patterns
|
||||
- Better for code generation
|
||||
- Forces consideration of every prop
|
||||
|
||||
#### 2. No Barrel Exports
|
||||
|
||||
Components are imported directly without `index.ts` files:
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
|
||||
// Bad (not allowed)
|
||||
import { Button } from '@tpmjs/ui';
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Clearer dependency graphs
|
||||
- Better tree-shaking
|
||||
- Prevents circular dependencies
|
||||
- Explicit imports
|
||||
|
||||
#### 3. Module Boundaries
|
||||
|
||||
ESLint enforces strict module boundaries:
|
||||
- Apps can only import from published packages
|
||||
- Packages cannot import from apps
|
||||
- UI package cannot import from utils (stays dependency-free)
|
||||
|
||||
#### 4. Shared Configurations
|
||||
|
||||
All configuration is centralized in `packages/config/`:
|
||||
- **Biome** - Formatting + basic linting
|
||||
- **ESLint** - Semantic rules and module boundaries
|
||||
- **Tailwind** - Design tokens and shared theme
|
||||
- **TypeScript** - Multiple configs for different contexts
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Run development servers
|
||||
pnpm dev
|
||||
|
||||
# Build all packages
|
||||
pnpm build
|
||||
|
||||
# Run tests
|
||||
pnpm test
|
||||
|
||||
# Lint and format
|
||||
pnpm lint
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### Component Development
|
||||
|
||||
1. Create component in `packages/ui/src/ComponentName/ComponentName.ts`
|
||||
2. Use `.ts` extension with `createElement`
|
||||
3. Add tests in `ComponentName.test.ts`
|
||||
4. Export in `package.json` exports map
|
||||
5. Add Storybook story in `packages/storybook/stories/`
|
||||
|
||||
### Publishing Flow
|
||||
|
||||
1. Make changes to packages
|
||||
2. Create changeset: `pnpm changeset`
|
||||
3. Version packages: `pnpm changeset:version`
|
||||
4. Publish to npm: `pnpm changeset:publish`
|
||||
5. Push with tags: `git push --follow-tags`
|
||||
|
||||
### Tech Stack
|
||||
|
||||
- **Build System:** Turborepo
|
||||
- **Package Manager:** pnpm
|
||||
- **TypeScript:** Strict mode, composite projects
|
||||
- **React:** v19 with .ts-only components
|
||||
- **Next.js:** v16 App Router
|
||||
- **Styling:** Tailwind CSS
|
||||
- **Testing:** Vitest + Testing Library
|
||||
- **Linting:** Biome + ESLint
|
||||
- **Documentation:** Storybook
|
||||
- **CI/CD:** GitHub Actions + Changesets
|
||||
- **Git Hooks:** Lefthook
|
||||
181
README.md
Normal file
181
README.md
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
# TPMJS Monorepo
|
||||
|
||||
Tool Package Manager for AI Agents - A Turborepo monorepo with strict TypeScript, Next.js 16, and best practices.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
apps/
|
||||
web/ - Next.js 16 App Router application
|
||||
packages/
|
||||
config/ - Shared configurations (Biome, ESLint, Tailwind, TypeScript)
|
||||
ui/ - React component library (.ts-only, no barrels)
|
||||
utils/ - Utility functions
|
||||
types/ - Shared TypeScript types
|
||||
env/ - Zod environment schema loader
|
||||
test/ - Vitest shared configuration
|
||||
mocks/ - MSW mock server
|
||||
storybook/ - Storybook documentation
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js >= 18
|
||||
- pnpm >= 8
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Run all apps in development mode
|
||||
pnpm dev
|
||||
|
||||
# Run specific app
|
||||
pnpm --filter @tpmjs/web dev
|
||||
pnpm --filter @tpmjs/storybook dev
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Build all packages and apps
|
||||
pnpm build
|
||||
|
||||
# Build specific package
|
||||
pnpm --filter @tpmjs/ui build
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Run tests in watch mode with UI
|
||||
pnpm test:ui
|
||||
```
|
||||
|
||||
### Linting & Formatting
|
||||
|
||||
```bash
|
||||
# Lint all packages
|
||||
pnpm lint
|
||||
|
||||
# Format all files
|
||||
pnpm format
|
||||
|
||||
# Check formatting
|
||||
pnpm format:check
|
||||
```
|
||||
|
||||
## Component Usage
|
||||
|
||||
Components are imported directly without barrel exports:
|
||||
|
||||
```typescript
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Card, CardHeader } from '@tpmjs/ui/Card/Card';
|
||||
```
|
||||
|
||||
**Important:** All UI components use `.ts` extension (not `.tsx`) and use `createElement` instead of JSX.
|
||||
|
||||
## Publishing Workflow
|
||||
|
||||
### 1. Create Changesets
|
||||
|
||||
After making changes to publishable packages:
|
||||
|
||||
```bash
|
||||
pnpm changeset
|
||||
```
|
||||
|
||||
Follow the prompts to describe your changes and select which packages are affected.
|
||||
|
||||
### 2. Version Packages
|
||||
|
||||
When ready to release:
|
||||
|
||||
```bash
|
||||
pnpm changeset:version
|
||||
```
|
||||
|
||||
This updates package versions and generates CHANGELOGs.
|
||||
|
||||
### 3. Publish to npm
|
||||
|
||||
```bash
|
||||
pnpm changeset:publish
|
||||
```
|
||||
|
||||
This builds and publishes all packages with changesets to npm.
|
||||
|
||||
### 4. Push to GitHub
|
||||
|
||||
```bash
|
||||
git push --follow-tags
|
||||
```
|
||||
|
||||
## Published Packages
|
||||
|
||||
- `@tpmjs/ui` - React component library
|
||||
- `@tpmjs/utils` - Utility functions
|
||||
- `@tpmjs/types` - TypeScript types
|
||||
- `@tpmjs/env` - Environment schema loader
|
||||
|
||||
## Module Boundaries
|
||||
|
||||
ESLint enforces module boundaries:
|
||||
|
||||
- Apps can import from published packages only
|
||||
- Packages cannot import from apps
|
||||
- No barrel exports (`index.ts`) allowed
|
||||
- Direct imports required: `@tpmjs/ui/Button/Button`
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### Why .ts-only Components?
|
||||
|
||||
Using `.ts` instead of `.tsx` for React components:
|
||||
- Enforces explicit `createElement` calls
|
||||
- Makes React's runtime nature more visible
|
||||
- Prevents JSX spreading anti-patterns
|
||||
- Better for code generation and tooling
|
||||
|
||||
### Why No Barrel Exports?
|
||||
|
||||
- Clearer dependency graphs
|
||||
- Better tree-shaking
|
||||
- Explicit imports show what's actually used
|
||||
- Prevents circular dependencies
|
||||
|
||||
### Why Biome + ESLint?
|
||||
|
||||
- Biome: Fast formatting and basic linting
|
||||
- ESLint: Semantic rules (module boundaries, TypeScript strictness)
|
||||
- Each tool focuses on what it does best
|
||||
|
||||
## Scripts Reference
|
||||
|
||||
- `dev` - Start development servers
|
||||
- `build` - Build all packages
|
||||
- `test` - Run tests
|
||||
- `test:ui` - Run tests with UI
|
||||
- `lint` - Lint code
|
||||
- `format` - Format code with Biome
|
||||
- `format:check` - Check formatting
|
||||
- `type-check` - TypeScript type checking
|
||||
- `clean` - Remove build artifacts
|
||||
- `changeset` - Create a changeset
|
||||
- `changeset:version` - Version packages
|
||||
- `changeset:publish` - Publish to npm
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
3
apps/web/eslint.config.js
Normal file
3
apps/web/eslint.config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import reactConfig from '@tpmjs/eslint-config/react.js';
|
||||
|
||||
export default reactConfig;
|
||||
8
apps/web/next.config.ts
Normal file
8
apps/web/next.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import type { NextConfig } from 'next';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: ['@tpmjs/ui', '@tpmjs/utils'],
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
36
apps/web/package.json
Normal file
36
apps/web/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@tpmjs/web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf .next .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tpmjs/env": "workspace:*",
|
||||
"@tpmjs/types": "workspace:*",
|
||||
"@tpmjs/ui": "workspace:*",
|
||||
"@tpmjs/utils": "workspace:*",
|
||||
"next": "^16.0.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/eslint-config": "workspace:*",
|
||||
"@tpmjs/tailwind-config": "workspace:*",
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint-config-next": "^16.0.4",
|
||||
"postcss": "^8.5.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
6
apps/web/postcss.config.js
Normal file
6
apps/web/postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
30
apps/web/src/app/globals.css
Normal file
30
apps/web/src/app/globals.css
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
19
apps/web/src/app/layout.tsx
Normal file
19
apps/web/src/app/layout.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'TPMJS - Tool Package Manager for AI Agents',
|
||||
description: 'The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
38
apps/web/src/app/page.tsx
Normal file
38
apps/web/src/app/page.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@tpmjs/ui/Card/Card';
|
||||
import { createElement } from 'react';
|
||||
|
||||
export default function HomePage(): React.ReactElement {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center p-24">
|
||||
<div className="space-y-8 max-w-2xl">
|
||||
<h1 className="text-4xl font-bold text-center">TPMJS</h1>
|
||||
<p className="text-xl text-center text-muted-foreground">
|
||||
Tool Package Manager for AI Agents
|
||||
</p>
|
||||
|
||||
{createElement(
|
||||
Card,
|
||||
{},
|
||||
createElement(
|
||||
CardHeader,
|
||||
{},
|
||||
createElement(CardTitle, {}, 'Welcome to TPMJS')
|
||||
),
|
||||
createElement(
|
||||
CardContent,
|
||||
{ className: 'space-y-4' },
|
||||
createElement('p', {},
|
||||
'The registry for AI tools. Discover, share, and integrate tools that give your agents superpowers.'
|
||||
),
|
||||
createElement(
|
||||
Button,
|
||||
{ className: 'w-full' },
|
||||
'Get Started'
|
||||
)
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
7
apps/web/src/env.ts
Normal file
7
apps/web/src/env.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { createEnv } from '@tpmjs/env';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const env = createEnv({
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
NEXT_PUBLIC_API_URL: z.string().url().optional(),
|
||||
});
|
||||
11
apps/web/tailwind.config.ts
Normal file
11
apps/web/tailwind.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { Config } from 'tailwindcss';
|
||||
import baseConfig from '@tpmjs/tailwind-config/base';
|
||||
|
||||
export default {
|
||||
...baseConfig,
|
||||
content: [
|
||||
'./src/app/**/*.{ts,tsx}',
|
||||
'./src/components/**/*.{ts,tsx}',
|
||||
'../../packages/ui/src/**/*.ts',
|
||||
],
|
||||
} satisfies Config;
|
||||
16
apps/web/tsconfig.json
Normal file
16
apps/web/tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
18
lefthook.yml
Normal file
18
lefthook.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
pre-commit:
|
||||
parallel: true
|
||||
commands:
|
||||
format:
|
||||
glob: "*.{ts,tsx,js,jsx,json,md}"
|
||||
run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
|
||||
stage_fixed: true
|
||||
|
||||
type-check:
|
||||
run: pnpm type-check
|
||||
|
||||
lint:
|
||||
run: pnpm lint
|
||||
|
||||
pre-push:
|
||||
commands:
|
||||
test:
|
||||
run: pnpm test
|
||||
34
package.json
Normal file
34
package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "@tpmjs/monorepo",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"scripts": {
|
||||
"dev": "turbo dev",
|
||||
"build": "turbo build",
|
||||
"test": "turbo test",
|
||||
"test:ui": "turbo test:ui",
|
||||
"lint": "turbo lint",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome check .",
|
||||
"type-check": "turbo type-check",
|
||||
"clean": "turbo clean && rm -rf node_modules .turbo",
|
||||
"changeset": "changeset",
|
||||
"changeset:version": "changeset version && pnpm install --no-frozen-lockfile",
|
||||
"changeset:publish": "pnpm build && changeset publish",
|
||||
"prepare": "lefthook install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/cli": "^2.27.10",
|
||||
"@types/node": "^22.10.2",
|
||||
"lefthook": "^1.10.1",
|
||||
"turbo": "^2.6.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
41
packages/config/biome.json
Normal file
41
packages/config/biome.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 100
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"complexity": {
|
||||
"noExcessiveCognitiveComplexity": "warn",
|
||||
"noForEach": "off"
|
||||
},
|
||||
"style": {
|
||||
"useImportType": "error",
|
||||
"useNodejsImportProtocol": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "warn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"trailingCommas": "es5",
|
||||
"semicolons": "always"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
packages/config/eslint/base.js
Normal file
32
packages/config/eslint/base.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import js from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
|
||||
export default tseslint.config(
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
plugins: {
|
||||
import: importPlugin,
|
||||
},
|
||||
rules: {
|
||||
// Module boundaries
|
||||
'import/no-internal-modules': ['error', {
|
||||
allow: [
|
||||
'@tpmjs/ui/*/[!index]*',
|
||||
'@tpmjs/utils/*',
|
||||
'@tpmjs/types/*',
|
||||
]
|
||||
}],
|
||||
'import/no-restricted-paths': ['error', {
|
||||
zones: [
|
||||
{ target: './packages', from: './apps' },
|
||||
{ target: './packages/ui', from: './packages/utils' },
|
||||
]
|
||||
}],
|
||||
'import/no-anonymous-default-export': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
}
|
||||
);
|
||||
19
packages/config/eslint/package.json
Normal file
19
packages/config/eslint/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "@tpmjs/eslint-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./base.js",
|
||||
"files": [
|
||||
"base.js",
|
||||
"react.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.18.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"typescript-eslint": "^8.19.1"
|
||||
}
|
||||
}
|
||||
28
packages/config/eslint/react.js
vendored
Normal file
28
packages/config/eslint/react.js
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import reactPlugin from 'eslint-plugin-react';
|
||||
import reactHooksPlugin from 'eslint-plugin-react-hooks';
|
||||
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
||||
import baseConfig from './base.js';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
plugins: {
|
||||
react: reactPlugin,
|
||||
'react-hooks': reactHooksPlugin,
|
||||
'jsx-a11y': jsxA11y,
|
||||
},
|
||||
rules: {
|
||||
...reactPlugin.configs.recommended.rules,
|
||||
...reactHooksPlugin.configs.recommended.rules,
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/prop-types': 'off',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
11
packages/config/package.json
Normal file
11
packages/config/package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "@tpmjs/config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"biome.json",
|
||||
"eslint",
|
||||
"tailwind",
|
||||
"tsconfig"
|
||||
]
|
||||
}
|
||||
42
packages/config/tailwind/base.ts
Normal file
42
packages/config/tailwind/base.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import type { Config } from 'tailwindcss';
|
||||
|
||||
export default {
|
||||
content: [],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config;
|
||||
15
packages/config/tailwind/package.json
Normal file
15
packages/config/tailwind/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@tpmjs/tailwind-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./base.ts",
|
||||
"files": [
|
||||
"base.ts"
|
||||
],
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3.4.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
28
packages/config/tsconfig/base.json
Normal file
28
packages/config/tsconfig/base.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"incremental": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["ES2022"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
".turbo"
|
||||
]
|
||||
}
|
||||
24
packages/config/tsconfig/nextjs.json
Normal file
24
packages/config/tsconfig/nextjs.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"target": "ES2022"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
10
packages/config/tsconfig/package.json
Normal file
10
packages/config/tsconfig/package.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@tpmjs/tsconfig",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"base.json",
|
||||
"nextjs.json",
|
||||
"react-library.json"
|
||||
]
|
||||
}
|
||||
8
packages/config/tsconfig/react-library.json
Normal file
8
packages/config/tsconfig/react-library.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||
}
|
||||
}
|
||||
32
packages/env/package.json
vendored
Normal file
32
packages/env/package.json
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "@tpmjs/env",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"@types/node": "^22.10.2",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
14
packages/env/src/index.ts
vendored
Normal file
14
packages/env/src/index.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export function createEnv<T extends z.ZodRawShape>(schema: T): z.infer<z.ZodObject<T>> {
|
||||
const envSchema = z.object(schema);
|
||||
|
||||
const parsed = envSchema.safeParse(process.env);
|
||||
|
||||
if (!parsed.success) {
|
||||
console.error('Invalid environment variables:', parsed.error.flatten().fieldErrors);
|
||||
throw new Error('Invalid environment variables');
|
||||
}
|
||||
|
||||
return parsed.data;
|
||||
}
|
||||
11
packages/env/tsconfig.json
vendored
Normal file
11
packages/env/tsconfig.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
9
packages/env/tsup.config.ts
vendored
Normal file
9
packages/env/tsup.config.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
});
|
||||
33
packages/mocks/package.json
Normal file
33
packages/mocks/package.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "@tpmjs/mocks",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": {
|
||||
"types": "./dist/server.d.ts",
|
||||
"default": "./dist/server.js"
|
||||
},
|
||||
"./handlers": {
|
||||
"types": "./dist/handlers.d.ts",
|
||||
"default": "./dist/handlers.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"msw": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
14
packages/mocks/src/handlers.ts
Normal file
14
packages/mocks/src/handlers.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
export const handlers = [
|
||||
http.get('/api/tools', () => {
|
||||
return HttpResponse.json([
|
||||
{
|
||||
id: 'web-search',
|
||||
name: 'Web Search',
|
||||
description: 'Search the web for information',
|
||||
category: 'search',
|
||||
},
|
||||
]);
|
||||
}),
|
||||
];
|
||||
4
packages/mocks/src/server.ts
Normal file
4
packages/mocks/src/server.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { setupServer } from 'msw/node';
|
||||
import { handlers } from './handlers';
|
||||
|
||||
export const server = setupServer(...handlers);
|
||||
11
packages/mocks/tsconfig.json
Normal file
11
packages/mocks/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
8
packages/mocks/tsup.config.ts
Normal file
8
packages/mocks/tsup.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/server.ts', 'src/handlers.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
});
|
||||
14
packages/test/package.json
Normal file
14
packages/test/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "@tpmjs/test",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./vitest": "./vitest.config.ts"
|
||||
},
|
||||
"files": [
|
||||
"vitest.config.ts"
|
||||
],
|
||||
"dependencies": {
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
11
packages/test/vitest.config.ts
Normal file
11
packages/test/vitest.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html'],
|
||||
},
|
||||
},
|
||||
});
|
||||
35
packages/types/package.json
Normal file
35
packages/types/package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "@tpmjs/types",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./tool": {
|
||||
"types": "./dist/tool.d.ts",
|
||||
"default": "./dist/tool.js"
|
||||
},
|
||||
"./registry": {
|
||||
"types": "./dist/registry.d.ts",
|
||||
"default": "./dist/registry.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
17
packages/types/src/registry.ts
Normal file
17
packages/types/src/registry.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const RegistrySearchResultSchema = z.object({
|
||||
toolId: z.string(),
|
||||
score: z.number(),
|
||||
matchType: z.enum(['exact', 'fuzzy', 'semantic']),
|
||||
});
|
||||
|
||||
export const RegistrySearchOptionsSchema = z.object({
|
||||
query: z.string(),
|
||||
category: z.string().optional(),
|
||||
limit: z.number().default(10),
|
||||
offset: z.number().default(0),
|
||||
});
|
||||
|
||||
export type RegistrySearchResult = z.infer<typeof RegistrySearchResultSchema>;
|
||||
export type RegistrySearchOptions = z.infer<typeof RegistrySearchOptionsSchema>;
|
||||
21
packages/types/src/tool.ts
Normal file
21
packages/types/src/tool.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export const ToolParameterSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
schema: z.record(z.unknown()),
|
||||
required: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const ToolSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
category: z.string(),
|
||||
version: z.string(),
|
||||
parameters: z.array(ToolParameterSchema),
|
||||
tags: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
export type Tool = z.infer<typeof ToolSchema>;
|
||||
export type ToolParameter = z.infer<typeof ToolParameterSchema>;
|
||||
11
packages/types/tsconfig.json
Normal file
11
packages/types/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
10
packages/types/tsup.config.ts
Normal file
10
packages/types/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/tool.ts', 'src/registry.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
splitting: false,
|
||||
});
|
||||
3
packages/ui/eslint.config.js
Normal file
3
packages/ui/eslint.config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import reactConfig from '@tpmjs/eslint-config/react.js';
|
||||
|
||||
export default reactConfig;
|
||||
51
packages/ui/package.json
Normal file
51
packages/ui/package.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "@tpmjs/ui",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./Button/Button": {
|
||||
"types": "./dist/Button/Button.d.ts",
|
||||
"default": "./dist/Button/Button.js"
|
||||
},
|
||||
"./Card/Card": {
|
||||
"types": "./dist/Card/Card.d.ts",
|
||||
"default": "./dist/Card/Card.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui",
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tpmjs/utils": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/eslint-config": "workspace:*",
|
||||
"@tpmjs/test": "workspace:*",
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/react": "^19.0.2",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"happy-dom": "^15.11.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
23
packages/ui/src/Button/Button.test.ts
Normal file
23
packages/ui/src/Button/Button.test.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Button } from './Button';
|
||||
import { createElement } from 'react';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders with default variant', () => {
|
||||
render(createElement(Button, { children: 'Click me' }));
|
||||
expect(screen.getByRole('button')).toHaveTextContent('Click me');
|
||||
});
|
||||
|
||||
it('applies variant classes', () => {
|
||||
render(createElement(Button, { variant: 'destructive', children: 'Delete' }));
|
||||
const button = screen.getByRole('button');
|
||||
expect(button.className).toContain('bg-destructive');
|
||||
});
|
||||
|
||||
it('applies size classes', () => {
|
||||
render(createElement(Button, { size: 'lg', children: 'Large' }));
|
||||
const button = screen.getByRole('button');
|
||||
expect(button.className).toContain('h-11');
|
||||
});
|
||||
});
|
||||
40
packages/ui/src/Button/Button.ts
Normal file
40
packages/ui/src/Button/Button.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { createElement, forwardRef, type ButtonHTMLAttributes } from 'react';
|
||||
import { cn } from '@tpmjs/utils/cn';
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
||||
size?: 'default' | 'sm' | 'lg' | 'icon';
|
||||
}
|
||||
|
||||
const buttonVariants = {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
};
|
||||
|
||||
const buttonSizes = {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = 'default', size = 'default', ...props }, ref) => {
|
||||
return createElement('button', {
|
||||
className: cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
buttonVariants[variant],
|
||||
buttonSizes[size],
|
||||
className
|
||||
),
|
||||
ref,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
53
packages/ui/src/Card/Card.ts
Normal file
53
packages/ui/src/Card/Card.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { createElement, forwardRef, type HTMLAttributes } from 'react';
|
||||
import { cn } from '@tpmjs/utils/cn';
|
||||
|
||||
export interface CardProps extends HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export const Card = forwardRef<HTMLDivElement, CardProps>(({ className, ...props }, ref) => {
|
||||
return createElement('div', {
|
||||
ref,
|
||||
className: cn(
|
||||
'rounded-lg border border-border bg-card text-card-foreground shadow-sm',
|
||||
className
|
||||
),
|
||||
...props,
|
||||
});
|
||||
});
|
||||
|
||||
Card.displayName = 'Card';
|
||||
|
||||
export const CardHeader = forwardRef<HTMLDivElement, CardProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return createElement('div', {
|
||||
ref,
|
||||
className: cn('flex flex-col space-y-1.5 p-6', className),
|
||||
...props,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
export const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
return createElement('h3', {
|
||||
ref,
|
||||
className: cn('text-2xl font-semibold leading-none tracking-tight', className),
|
||||
...props,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
CardTitle.displayName = 'CardTitle';
|
||||
|
||||
export const CardContent = forwardRef<HTMLDivElement, CardProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return createElement('div', {
|
||||
ref,
|
||||
className: cn('p-6 pt-0', className),
|
||||
...props,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
CardContent.displayName = 'CardContent';
|
||||
11
packages/ui/tsconfig.json
Normal file
11
packages/ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/react-library.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
14
packages/ui/tsup.config.ts
Normal file
14
packages/ui/tsup.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/Button/Button.ts', 'src/Card/Card.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
splitting: false,
|
||||
external: ['react', 'react-dom'],
|
||||
esbuildOptions(options) {
|
||||
options.jsx = 'automatic';
|
||||
},
|
||||
});
|
||||
8
packages/ui/vitest.config.ts
Normal file
8
packages/ui/vitest.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
});
|
||||
40
packages/utils/package.json
Normal file
40
packages/utils/package.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "@tpmjs/utils",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./cn": {
|
||||
"types": "./dist/cn.d.ts",
|
||||
"default": "./dist/cn.js"
|
||||
},
|
||||
"./format": {
|
||||
"types": "./dist/format.d.ts",
|
||||
"default": "./dist/format.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/test": "workspace:*",
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
16
packages/utils/src/cn.test.ts
Normal file
16
packages/utils/src/cn.test.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { cn } from './cn';
|
||||
|
||||
describe('cn', () => {
|
||||
it('merges class names', () => {
|
||||
expect(cn('foo', 'bar')).toBe('foo bar');
|
||||
});
|
||||
|
||||
it('handles conditional classes', () => {
|
||||
expect(cn('foo', false && 'bar', 'baz')).toBe('foo baz');
|
||||
});
|
||||
|
||||
it('merges tailwind classes correctly', () => {
|
||||
expect(cn('px-2 py-1', 'px-4')).toBe('py-1 px-4');
|
||||
});
|
||||
});
|
||||
6
packages/utils/src/cn.ts
Normal file
6
packages/utils/src/cn.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
11
packages/utils/src/format.ts
Normal file
11
packages/utils/src/format.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export function formatDate(date: Date): string {
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export function formatNumber(num: number): string {
|
||||
return new Intl.NumberFormat('en-US').format(num);
|
||||
}
|
||||
11
packages/utils/tsconfig.json
Normal file
11
packages/utils/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
10
packages/utils/tsup.config.ts
Normal file
10
packages/utils/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/cn.ts', 'src/format.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
splitting: false,
|
||||
});
|
||||
8
packages/utils/vitest.config.ts
Normal file
8
packages/utils/vitest.config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
8131
pnpm-lock.yaml
generated
Normal file
8131
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
6
pnpm-workspace.yaml
Normal file
6
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
packages:
|
||||
- 'apps/*'
|
||||
- 'packages/*'
|
||||
- 'packages/config/eslint'
|
||||
- 'packages/config/tailwind'
|
||||
- 'packages/config/tsconfig'
|
||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./apps/web" },
|
||||
{ "path": "./packages/ui" },
|
||||
{ "path": "./packages/utils" },
|
||||
{ "path": "./packages/types" },
|
||||
{ "path": "./packages/env" },
|
||||
{ "path": "./packages/test" },
|
||||
{ "path": "./packages/mocks" },
|
||||
{ "path": "./packages/storybook" }
|
||||
]
|
||||
}
|
||||
35
turbo.json
Normal file
35
turbo.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"globalDependencies": [
|
||||
"**/.env.*local",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**", "!.next/cache/**", "storybook-static/**"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"type-check": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": []
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["coverage/**"]
|
||||
},
|
||||
"test:ui": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue