development-monorepo-pnpm skill: initial import
Full-stack pnpm + Turborepo monorepo skill, independent of the separate backend and frontend monorepo skills it draws on. Repository shape is split four ways at the top level: applications-backend, applications-frontend, packages-backend, packages-frontend — so a path states which runtime a file ends up in. Defaults: NestJS for backend services, React with IBM Carbon Design for browser applications; the project's stated architecture overrides both. Covers workspace globs, the Turborepo pipeline, filtered validation, blast-radius checks, Module Federation, turbo prune image builds, and Helm. Documents one sanctioned cross-boundary dependency: API contract types owned by the backend, imported type-only by the frontend. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@ca/eslint-config/base"],
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
# example-shared-package — `@ca/example-utils`
|
||||
|
||||
Complete minimal shared TypeScript package for a pnpm + Turborepo monorepo. Copy it as the
|
||||
starting point for a new package and rename the scope to match your repository.
|
||||
|
||||
It goes under **`packages-backend/`** or **`packages-frontend/`** depending on who consumes
|
||||
it — the root matches the consumers, never the author. See `references/packages.md` §1.
|
||||
|
||||
The package itself is environment-neutral: no Node built-ins, no DOM APIs. That is what makes
|
||||
it a safe template for either root. Adding `fs`, `path`, or `crypto` makes it backend-only;
|
||||
adding DOM APIs makes it frontend-only.
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
packages-backend/example-utils/ # or packages-frontend/example-utils/
|
||||
├── .eslintrc.cjs
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── src/
|
||||
├── index.ts # Public barrel — re-exports types and utils
|
||||
├── types.ts # Shared TypeScript types
|
||||
└── example-utils.ts # Utility functions
|
||||
```
|
||||
|
||||
## Key conventions
|
||||
|
||||
- `"type": "module"` with ESM output under `dist/`.
|
||||
- `exports` maps the package root to `dist/index.js` and `dist/index.d.ts`.
|
||||
- `tsconfig.json` extends the shared `typescript-config` preset — only `outDir` and `rootDir`
|
||||
are local.
|
||||
- `.eslintrc.cjs` extends the shared `eslint-config` preset.
|
||||
- Internal imports carry the `.js` extension — required for ESM TypeScript.
|
||||
- `files: ["dist"]` keeps sources out of the published payload.
|
||||
- Workspace dependencies use `workspace:*`.
|
||||
- `src/index.ts` is the entire public API; nothing else is importable by consumers.
|
||||
- Script names (`build`, `check-types`, `lint`) match the tasks in `turbo.json`, or Turborepo
|
||||
skips the workspace silently.
|
||||
|
||||
## Contract packages
|
||||
|
||||
A package that shares API types across the front/back boundary is a variant of this template:
|
||||
name it `<domain>-contracts`, place it under `packages-backend/`, export **types only**, and
|
||||
keep it free of runtime dependencies. Frontend consumers import it with `import type`.
|
||||
|
||||
## Validation commands
|
||||
|
||||
```bash
|
||||
pnpm check-types --filter="@ca/example-utils"
|
||||
pnpm lint --filter="@ca/example-utils"
|
||||
pnpm build --filter="@ca/example-utils"
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@ca/example-utils",
|
||||
"version": "1.0.0",
|
||||
"description": "Generic example shared utility package for CogArchHub",
|
||||
"author": "IBM",
|
||||
"license": "IBM copyright",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.edst.ibm.com/repository/Cognitive_Architect/"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"check-types": "tsc --noEmit",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ca/eslint-config": "workspace:*",
|
||||
"@ca/typescript-config": "workspace:*",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { ExampleItem, ExampleResult } from "./types.js";
|
||||
|
||||
/**
|
||||
* Wraps a value in an ExampleResult with an ISO timestamp.
|
||||
*/
|
||||
export const wrapResult = <T>(data: T): ExampleResult<T> => ({
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds an item by id from a list.
|
||||
* Returns undefined when not found.
|
||||
*/
|
||||
export const findById = (
|
||||
items: ExampleItem[],
|
||||
id: string,
|
||||
): ExampleItem | undefined => items.find((item) => item.id === id);
|
||||
|
||||
/**
|
||||
* Converts an ExampleItem to a display string.
|
||||
*/
|
||||
export const toDisplayString = (item: ExampleItem): string =>
|
||||
`[${item.id}] ${item.name}: ${item.value}`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./types.js";
|
||||
export * from "./example-utils.js";
|
||||
@@ -0,0 +1,10 @@
|
||||
export type ExampleItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ExampleResult<T> = {
|
||||
data: T;
|
||||
timestamp: string;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@ca/typescript-config/base",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user