@simpill/utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +787 -0
- package/README.md +186 -0
- package/__tests__/README.md +32 -0
- package/__tests__/e2e/all-packages-resolve.e2e.test.ts +40 -0
- package/__tests__/integration/env-and-async.integration.test.ts +12 -0
- package/__tests__/integration/errors-and-uuid.integration.test.ts +14 -0
- package/__tests__/integration/object-and-array.integration.test.ts +15 -0
- package/__tests__/unit/@simpill/_resolver/resolve-packages.unit.test.ts +47 -0
- package/__tests__/unit/@simpill/array.utils/array.utils.unit.test.ts +11 -0
- package/__tests__/unit/@simpill/async.utils/async.utils.unit.test.ts +12 -0
- package/__tests__/unit/@simpill/cache.utils/cache.utils.unit.test.ts +21 -0
- package/__tests__/unit/@simpill/env.utils/env.utils.unit.test.ts +13 -0
- package/__tests__/unit/@simpill/errors.utils/errors.utils.unit.test.ts +13 -0
- package/__tests__/unit/@simpill/object.utils/object.utils.unit.test.ts +11 -0
- package/__tests__/unit/@simpill/patterns.utils/patterns.utils.unit.test.ts +23 -0
- package/__tests__/unit/@simpill/string.utils/string.utils.unit.test.ts +11 -0
- package/__tests__/unit/@simpill/time.utils/time.utils.unit.test.ts +12 -0
- package/__tests__/unit/@simpill/uuid.utils/uuid.utils.unit.test.ts +12 -0
- package/docs/PUBLISHING_AND_PACKAGES.md +258 -0
- package/docs/template/.env.sample +0 -0
- package/docs/template/README.md +0 -0
- package/docs/template/TEMPLATE.md +1040 -0
- package/docs/template/assets/logo-banner.svg +20 -0
- package/docs/template/package.json +14 -0
- package/index.ts +89 -0
- package/package.json +87 -0
- package/scripts/README.md +57 -0
- package/scripts/github/github-set-all-topics.js +120 -0
- package/scripts/github/github-set-repo-topics.sh +33 -0
- package/scripts/github/github-set-repos-public.sh +71 -0
- package/scripts/lib/package-topics.js +57 -0
- package/scripts/lib/publish-order.js +140 -0
- package/scripts/lib/sync-repo-links.js +75 -0
- package/scripts/monorepo/install-hooks.sh +64 -0
- package/scripts/monorepo/monorepo-clean.sh +7 -0
- package/scripts/monorepo/monorepo-sync-deps.js +81 -0
- package/scripts/monorepo/monorepo-verify-deps.js +37 -0
- package/scripts/monorepo/use-local-utils-at-root.js +49 -0
- package/scripts/publish/publish-all.sh +152 -0
- package/scripts/utils/utils-fix-repo-metadata.js +61 -0
- package/scripts/utils/utils-prepare-all.sh +107 -0
- package/scripts/utils/utils-set-npm-keywords.js +132 -0
- package/scripts/utils/utils-update-readme-badges.js +83 -0
- package/scripts/utils/utils-use-local-deps.js +43 -0
- package/scripts/utils/utils-verify-all.sh +45 -0
- package/tsconfig.json +14 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
# Contributing to @simpill
|
|
2
|
+
|
|
3
|
+
This guide explains how to create new packages in the `@simpill` monorepo.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Package Naming](#package-naming)
|
|
8
|
+
- [Directory Structure](#directory-structure)
|
|
9
|
+
- [Required Files](#required-files)
|
|
10
|
+
- [Configuration Files](#configuration-files)
|
|
11
|
+
- [Scripts](#scripts)
|
|
12
|
+
- [Testing Requirements](#testing-requirements)
|
|
13
|
+
- [Code Style](#code-style)
|
|
14
|
+
- [Subpath Exports](#subpath-exports)
|
|
15
|
+
- [CI/CD Setup](#cicd-setup)
|
|
16
|
+
- [Checklist](#checklist)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Package Naming
|
|
21
|
+
|
|
22
|
+
All packages follow the naming convention: `@simpill/{name}.utils`
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
- `@simpill/env.utils`
|
|
26
|
+
- `@simpill/logger.utils`
|
|
27
|
+
- `@simpill/cache.utils`
|
|
28
|
+
- `@simpill/http.utils`
|
|
29
|
+
|
|
30
|
+
Packages live under `utils/@simpill-`; the directory name matches the package suffix (e.g., `utils/@simpill-env.utils/` for `@simpill/env.utils`).
|
|
31
|
+
|
|
32
|
+
### Tools and standalone packages
|
|
33
|
+
|
|
34
|
+
Some deliverables are **standalone GitHub packages** (their own repo, not in this workspace):
|
|
35
|
+
|
|
36
|
+
- **Image AI toolkit:** [ai-image-generated-ai-cli](https://github.com/simpill/ai-image-generated-ai-cli) — referenced from this monorepo root `package.json` via `github:simpill/ai-image-generated-ai-cli`; CLI available as `npx ai-image-gen` after `npm install`.
|
|
37
|
+
- **Sandbox apps:** [simpill-sandbox](https://github.com/simpill/simpill-sandbox) — todo-app and other demo apps; **not** referenced from this root (no workspace or dependency). Develop and run in that repo.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Directory Structure
|
|
42
|
+
|
|
43
|
+
Every package MUST follow this structure:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
{name}.utils/
|
|
47
|
+
├── __tests__/ # Test files (mirrors src/ structure)
|
|
48
|
+
│ ├── client/ # Client-side tests (if applicable)
|
|
49
|
+
│ │ ├── unit/
|
|
50
|
+
│ │ │ └── *.unit.test.ts
|
|
51
|
+
│ │ └── integration/
|
|
52
|
+
│ │ └── *.integration.test.ts
|
|
53
|
+
│ ├── server/ # Server-side tests (if applicable)
|
|
54
|
+
│ │ ├── unit/
|
|
55
|
+
│ │ │ └── *.unit.test.ts
|
|
56
|
+
│ │ └── integration/
|
|
57
|
+
│ │ └── *.integration.test.ts
|
|
58
|
+
│ └── shared/ # Shared utility tests
|
|
59
|
+
│ └── unit/
|
|
60
|
+
│ └── *.unit.test.ts
|
|
61
|
+
├── scripts/ # Shell scripts
|
|
62
|
+
│ ├── check.sh # Pre-push verification script
|
|
63
|
+
│ ├── install-hooks.sh # Git hooks installer
|
|
64
|
+
│ └── pre-push.sh # Pre-push hook
|
|
65
|
+
├── src/ # Source code
|
|
66
|
+
│ ├── client/ # Client/Edge runtime code (no fs access)
|
|
67
|
+
│ │ └── index.ts # Client exports
|
|
68
|
+
│ ├── server/ # Server/Node.js code (full access)
|
|
69
|
+
│ │ └── index.ts # Server exports
|
|
70
|
+
│ ├── shared/ # Shared utilities (runtime-agnostic)
|
|
71
|
+
│ │ └── index.ts # Shared exports
|
|
72
|
+
│ ├── index.ts # Main entry point (re-exports all)
|
|
73
|
+
│ └── *.d.ts # Type declarations (if needed)
|
|
74
|
+
├── dist/ # Build output (git-ignored)
|
|
75
|
+
├── coverage/ # Coverage reports (git-ignored)
|
|
76
|
+
├── node_modules/ # Dependencies (git-ignored)
|
|
77
|
+
├── .cursorrules # Cursor IDE rules (optional)
|
|
78
|
+
├── .gitignore # Git ignore patterns
|
|
79
|
+
├── .npmignore # npm publish ignore patterns
|
|
80
|
+
├── AGENTS.md # AI agent guidelines for this package
|
|
81
|
+
├── biome.json # Biome linter/formatter config
|
|
82
|
+
├── CLAUDE.md # Claude-specific instructions
|
|
83
|
+
├── jest.config.js # Jest test configuration
|
|
84
|
+
├── Makefile # Make commands (optional)
|
|
85
|
+
├── package.json # Package manifest
|
|
86
|
+
├── README.md # Package documentation
|
|
87
|
+
└── tsconfig.json # TypeScript configuration
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Source Code Organization
|
|
91
|
+
|
|
92
|
+
The `src/` directory uses a **runtime-based** organization:
|
|
93
|
+
|
|
94
|
+
| Directory | Purpose | Can use `fs`? | Example |
|
|
95
|
+
|-----------|---------|---------------|---------|
|
|
96
|
+
| `client/` | Edge Runtime, browser, middleware | No | Edge env helpers |
|
|
97
|
+
| `server/` | Node.js, API routes, server components | Yes | File-based config |
|
|
98
|
+
| `shared/` | Runtime-agnostic utilities | No | Parse helpers, types |
|
|
99
|
+
|
|
100
|
+
### Test File Naming
|
|
101
|
+
|
|
102
|
+
Test files MUST follow this naming convention:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
{feature}.unit.test.ts # Unit tests (isolated, mocked dependencies)
|
|
106
|
+
{feature}.integration.test.ts # Integration tests (real dependencies)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Required Files
|
|
112
|
+
|
|
113
|
+
### package.json
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"name": "@simpill/{name}.utils",
|
|
118
|
+
"version": "1.0.0",
|
|
119
|
+
"description": "Brief description of the package",
|
|
120
|
+
"main": "dist/index.js",
|
|
121
|
+
"types": "dist/index.d.ts",
|
|
122
|
+
"exports": {
|
|
123
|
+
".": {
|
|
124
|
+
"types": "./dist/index.d.ts",
|
|
125
|
+
"import": "./dist/index.js",
|
|
126
|
+
"require": "./dist/index.js"
|
|
127
|
+
},
|
|
128
|
+
"./client": {
|
|
129
|
+
"types": "./dist/client/index.d.ts",
|
|
130
|
+
"import": "./dist/client/index.js",
|
|
131
|
+
"require": "./dist/client/index.js"
|
|
132
|
+
},
|
|
133
|
+
"./server": {
|
|
134
|
+
"types": "./dist/server/index.d.ts",
|
|
135
|
+
"import": "./dist/server/index.js",
|
|
136
|
+
"require": "./dist/server/index.js"
|
|
137
|
+
},
|
|
138
|
+
"./shared": {
|
|
139
|
+
"types": "./dist/shared/index.d.ts",
|
|
140
|
+
"import": "./dist/shared/index.js",
|
|
141
|
+
"require": "./dist/shared/index.js"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"files": [
|
|
145
|
+
"dist",
|
|
146
|
+
"README.md"
|
|
147
|
+
],
|
|
148
|
+
"scripts": {
|
|
149
|
+
"build": "tsc",
|
|
150
|
+
"prepublishOnly": "npm run build",
|
|
151
|
+
"test": "jest",
|
|
152
|
+
"test:watch": "jest --watch",
|
|
153
|
+
"test:coverage": "jest --coverage",
|
|
154
|
+
"lint": "biome lint .",
|
|
155
|
+
"lint:fix": "biome lint --write .",
|
|
156
|
+
"format": "biome format --write .",
|
|
157
|
+
"format:check": "biome format .",
|
|
158
|
+
"check": "biome check .",
|
|
159
|
+
"check:fix": "biome check --write .",
|
|
160
|
+
"verify": "./scripts/check.sh",
|
|
161
|
+
"prepare": "./scripts/install-hooks.sh"
|
|
162
|
+
},
|
|
163
|
+
"keywords": [
|
|
164
|
+
"typescript",
|
|
165
|
+
"type-safe",
|
|
166
|
+
"{relevant-keywords}"
|
|
167
|
+
],
|
|
168
|
+
"author": "",
|
|
169
|
+
"license": "ISC",
|
|
170
|
+
"sideEffects": false,
|
|
171
|
+
"devDependencies": {
|
|
172
|
+
"@biomejs/biome": "^2.3.11",
|
|
173
|
+
"@types/jest": "^30.0.0",
|
|
174
|
+
"@types/node": "^20.11.0",
|
|
175
|
+
"jest": "^30.2.0",
|
|
176
|
+
"ts-jest": "^29.4.6",
|
|
177
|
+
"typescript": "^5.3.3"
|
|
178
|
+
},
|
|
179
|
+
"peerDependencies": {
|
|
180
|
+
"typescript": ">=4.0.0"
|
|
181
|
+
},
|
|
182
|
+
"engines": {
|
|
183
|
+
"node": ">=16.0.0"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- **`sideEffects": false`** — Tells bundlers (webpack, Rollup, Vite) the package is side-effect free so unused exports can be tree-shaken. Use this for pure utility packages.
|
|
189
|
+
|
|
190
|
+
### tsconfig.json
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"compilerOptions": {
|
|
195
|
+
"target": "ES2020",
|
|
196
|
+
"module": "commonjs",
|
|
197
|
+
"lib": ["ES2020"],
|
|
198
|
+
"types": ["node", "jest"],
|
|
199
|
+
"declaration": true,
|
|
200
|
+
"declarationMap": true,
|
|
201
|
+
"sourceMap": true,
|
|
202
|
+
"outDir": "./dist",
|
|
203
|
+
"rootDir": "./src",
|
|
204
|
+
"strict": true,
|
|
205
|
+
"esModuleInterop": true,
|
|
206
|
+
"skipLibCheck": true,
|
|
207
|
+
"forceConsistentCasingInFileNames": true,
|
|
208
|
+
"resolveJsonModule": true,
|
|
209
|
+
"moduleResolution": "node"
|
|
210
|
+
},
|
|
211
|
+
"include": ["src/**/*"],
|
|
212
|
+
"exclude": ["node_modules", "dist", "__tests__"]
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### jest.config.js
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
/** @type {import('jest').Config} */
|
|
220
|
+
module.exports = {
|
|
221
|
+
preset: "ts-jest",
|
|
222
|
+
testEnvironment: "node",
|
|
223
|
+
roots: ["<rootDir>/src", "<rootDir>/__tests__"],
|
|
224
|
+
testMatch: ["**/__tests__/**/*.test.ts", "**/?(*.)+(spec|test).ts"],
|
|
225
|
+
collectCoverageFrom: [
|
|
226
|
+
"src/**/*.ts",
|
|
227
|
+
"!src/**/*.d.ts",
|
|
228
|
+
"!src/**/*.test.ts",
|
|
229
|
+
"!src/**/*.spec.ts"
|
|
230
|
+
],
|
|
231
|
+
coverageDirectory: "coverage",
|
|
232
|
+
coverageReporters: ["text", "lcov", "html"],
|
|
233
|
+
coverageThreshold: {
|
|
234
|
+
global: {
|
|
235
|
+
branches: 80,
|
|
236
|
+
functions: 80,
|
|
237
|
+
lines: 80,
|
|
238
|
+
statements: 80
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"],
|
|
242
|
+
transform: {
|
|
243
|
+
"^.+\\.ts$": "ts-jest",
|
|
244
|
+
},
|
|
245
|
+
verbose: true,
|
|
246
|
+
clearMocks: true,
|
|
247
|
+
resetMocks: true,
|
|
248
|
+
restoreMocks: true,
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Use **coverageThreshold.global** with `branches`, `functions`, `lines`, and `statements` (80% default). Single-line `coverageThreshold: { global: { ... } }` and multi-line with nested `global: { ... }` are both acceptable; keep existing packages consistent with the template above.
|
|
253
|
+
|
|
254
|
+
### biome.json
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
|
259
|
+
"vcs": {
|
|
260
|
+
"enabled": true,
|
|
261
|
+
"clientKind": "git",
|
|
262
|
+
"useIgnoreFile": true
|
|
263
|
+
},
|
|
264
|
+
"files": {
|
|
265
|
+
"ignoreUnknown": false,
|
|
266
|
+
"includes": ["**", "!**/node_modules", "!**/dist", "!**/*.d.ts.map", "!**/*.js.map"]
|
|
267
|
+
},
|
|
268
|
+
"formatter": {
|
|
269
|
+
"enabled": true,
|
|
270
|
+
"indentStyle": "space",
|
|
271
|
+
"indentWidth": 2,
|
|
272
|
+
"lineEnding": "lf",
|
|
273
|
+
"lineWidth": 100,
|
|
274
|
+
"formatWithErrors": false
|
|
275
|
+
},
|
|
276
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
277
|
+
"linter": {
|
|
278
|
+
"enabled": true,
|
|
279
|
+
"rules": {
|
|
280
|
+
"recommended": true,
|
|
281
|
+
"complexity": {
|
|
282
|
+
"noBannedTypes": "error",
|
|
283
|
+
"noUselessTypeConstraint": "error"
|
|
284
|
+
},
|
|
285
|
+
"correctness": {
|
|
286
|
+
"noUnusedVariables": "error",
|
|
287
|
+
"useExhaustiveDependencies": "warn"
|
|
288
|
+
},
|
|
289
|
+
"style": {
|
|
290
|
+
"noParameterAssign": "error",
|
|
291
|
+
"useConst": "error",
|
|
292
|
+
"useTemplate": "error"
|
|
293
|
+
},
|
|
294
|
+
"suspicious": {
|
|
295
|
+
"noExplicitAny": "warn",
|
|
296
|
+
"noArrayIndexKey": "warn"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"javascript": {
|
|
301
|
+
"formatter": {
|
|
302
|
+
"quoteStyle": "double",
|
|
303
|
+
"jsxQuoteStyle": "double",
|
|
304
|
+
"quoteProperties": "asNeeded",
|
|
305
|
+
"trailingCommas": "es5",
|
|
306
|
+
"semicolons": "always",
|
|
307
|
+
"arrowParentheses": "always"
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"overrides": [
|
|
311
|
+
{
|
|
312
|
+
"includes": ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**"],
|
|
313
|
+
"linter": {
|
|
314
|
+
"rules": {
|
|
315
|
+
"suspicious": {
|
|
316
|
+
"noExplicitAny": "off"
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### .gitignore
|
|
326
|
+
|
|
327
|
+
```gitignore
|
|
328
|
+
# Dependencies
|
|
329
|
+
node_modules/
|
|
330
|
+
|
|
331
|
+
# Build output
|
|
332
|
+
dist/
|
|
333
|
+
|
|
334
|
+
# Coverage
|
|
335
|
+
coverage/
|
|
336
|
+
|
|
337
|
+
# Environment files
|
|
338
|
+
.env
|
|
339
|
+
.env.local
|
|
340
|
+
.env.*.local
|
|
341
|
+
|
|
342
|
+
# IDE
|
|
343
|
+
.idea/
|
|
344
|
+
*.swp
|
|
345
|
+
*.swo
|
|
346
|
+
|
|
347
|
+
# OS
|
|
348
|
+
.DS_Store
|
|
349
|
+
Thumbs.db
|
|
350
|
+
|
|
351
|
+
# Logs
|
|
352
|
+
*.log
|
|
353
|
+
npm-debug.log*
|
|
354
|
+
|
|
355
|
+
# Lock files (use package-lock.json)
|
|
356
|
+
yarn.lock
|
|
357
|
+
pnpm-lock.yaml
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### .npmignore
|
|
361
|
+
|
|
362
|
+
```npmignore
|
|
363
|
+
# Source files (dist is published)
|
|
364
|
+
src/
|
|
365
|
+
__tests__/
|
|
366
|
+
|
|
367
|
+
# Config files
|
|
368
|
+
.cursorrules
|
|
369
|
+
.gitignore
|
|
370
|
+
biome.json
|
|
371
|
+
jest.config.js
|
|
372
|
+
tsconfig.json
|
|
373
|
+
Makefile
|
|
374
|
+
scripts/
|
|
375
|
+
|
|
376
|
+
# Coverage
|
|
377
|
+
coverage/
|
|
378
|
+
|
|
379
|
+
# Documentation (keep README.md)
|
|
380
|
+
AGENTS.md
|
|
381
|
+
CLAUDE.md
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Scripts
|
|
387
|
+
|
|
388
|
+
### scripts/check.sh
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
#!/usr/bin/env bash
|
|
392
|
+
set -euo pipefail
|
|
393
|
+
|
|
394
|
+
cd "$(dirname "$0")/.."
|
|
395
|
+
|
|
396
|
+
echo "Running pre-push checks for {name}.utils"
|
|
397
|
+
echo "--------------------------------------"
|
|
398
|
+
|
|
399
|
+
echo "[1/5] Checking format..."
|
|
400
|
+
npm run format:check
|
|
401
|
+
echo "Format check passed."
|
|
402
|
+
|
|
403
|
+
echo "[2/5] Running linter..."
|
|
404
|
+
npm run lint
|
|
405
|
+
echo "Lint check passed."
|
|
406
|
+
|
|
407
|
+
echo "[3/5] Running type check..."
|
|
408
|
+
npx tsc --noEmit
|
|
409
|
+
echo "Type check passed."
|
|
410
|
+
|
|
411
|
+
echo "[4/5] Running tests..."
|
|
412
|
+
npm test
|
|
413
|
+
echo "Tests passed."
|
|
414
|
+
|
|
415
|
+
echo "[5/5] Verifying build..."
|
|
416
|
+
npm run build
|
|
417
|
+
echo "Build completed."
|
|
418
|
+
|
|
419
|
+
echo "--------------------------------------"
|
|
420
|
+
echo "All checks passed."
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### scripts/install-hooks.sh
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
#!/usr/bin/env bash
|
|
427
|
+
set -euo pipefail
|
|
428
|
+
|
|
429
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
430
|
+
PACKAGE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
431
|
+
REPO_ROOT="$(cd "$PACKAGE_DIR/.." && pwd)"
|
|
432
|
+
HOOKS_DIR="$REPO_ROOT/.git/hooks"
|
|
433
|
+
|
|
434
|
+
if [ ! -d "$HOOKS_DIR" ]; then
|
|
435
|
+
echo "Not a git repository or .git/hooks not found. Skipping hook installation."
|
|
436
|
+
exit 0
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
PRE_PUSH_HOOK="$HOOKS_DIR/pre-push"
|
|
440
|
+
|
|
441
|
+
cat > "$PRE_PUSH_HOOK" << 'EOF'
|
|
442
|
+
#!/usr/bin/env bash
|
|
443
|
+
set -euo pipefail
|
|
444
|
+
|
|
445
|
+
# Run checks for each package that has changes
|
|
446
|
+
for pkg_dir in env.utils logger.utils; do
|
|
447
|
+
if [ -d "$pkg_dir" ] && [ -f "$pkg_dir/scripts/check.sh" ]; then
|
|
448
|
+
if git diff --cached --name-only | grep -q "^$pkg_dir/"; then
|
|
449
|
+
echo "Running checks for $pkg_dir..."
|
|
450
|
+
(cd "$pkg_dir" && ./scripts/check.sh)
|
|
451
|
+
fi
|
|
452
|
+
fi
|
|
453
|
+
done
|
|
454
|
+
EOF
|
|
455
|
+
|
|
456
|
+
chmod +x "$PRE_PUSH_HOOK"
|
|
457
|
+
echo "Git hooks installed successfully."
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### scripts/pre-push.sh
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
#!/usr/bin/env bash
|
|
464
|
+
set -euo pipefail
|
|
465
|
+
|
|
466
|
+
cd "$(dirname "$0")/.."
|
|
467
|
+
./scripts/check.sh
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Make all scripts executable:
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
chmod +x scripts/*.sh
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Testing Requirements
|
|
479
|
+
|
|
480
|
+
### Coverage Thresholds
|
|
481
|
+
|
|
482
|
+
All packages MUST maintain **80% minimum coverage** across:
|
|
483
|
+
|
|
484
|
+
- **Statements**: 80%
|
|
485
|
+
- **Branches**: 80%
|
|
486
|
+
- **Functions**: 80%
|
|
487
|
+
- **Lines**: 80%
|
|
488
|
+
|
|
489
|
+
### Test Organization
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
__tests__/
|
|
493
|
+
├── client/
|
|
494
|
+
│ ├── unit/ # Fast, isolated tests
|
|
495
|
+
│ │ └── feature.unit.test.ts
|
|
496
|
+
│ └── integration/ # Tests with real dependencies
|
|
497
|
+
│ └── feature.integration.test.ts
|
|
498
|
+
├── server/
|
|
499
|
+
│ ├── unit/
|
|
500
|
+
│ └── integration/
|
|
501
|
+
└── shared/
|
|
502
|
+
└── unit/
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Async tests and timeouts
|
|
506
|
+
|
|
507
|
+
For **async-heavy tests** (e.g. `delay()`, retries, WebSockets, timers):
|
|
508
|
+
|
|
509
|
+
- Use **short delays** in unit tests (e.g. 1–50 ms) so CI stays fast.
|
|
510
|
+
- If a test needs more time, set **`jest.setTimeout(ms)`** at the top of the `describe` block or in `jest.config.js` (`testTimeout`). A common default is 5–10 seconds for unit tests; increase only for integration tests that hit real I/O.
|
|
511
|
+
- Prefer **fake timers** (`jest.useFakeTimers()`) where possible to avoid real waits.
|
|
512
|
+
|
|
513
|
+
### Test File Template
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
/**
|
|
517
|
+
* @file {Feature} Unit Tests
|
|
518
|
+
* @description Tests for {feature description}
|
|
519
|
+
*/
|
|
520
|
+
|
|
521
|
+
describe("{FeatureName}", () => {
|
|
522
|
+
beforeEach(() => {
|
|
523
|
+
// Setup
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
afterEach(() => {
|
|
527
|
+
// Cleanup
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
describe("{methodName}", () => {
|
|
531
|
+
it("should {expected behavior}", () => {
|
|
532
|
+
// Arrange
|
|
533
|
+
// Act
|
|
534
|
+
// Assert
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("should handle edge case: {description}", () => {
|
|
538
|
+
// Test edge cases
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("should throw when {error condition}", () => {
|
|
542
|
+
expect(() => {
|
|
543
|
+
// Action that should throw
|
|
544
|
+
}).toThrow(ExpectedError);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Code Style
|
|
553
|
+
|
|
554
|
+
### Enforced by Biome
|
|
555
|
+
|
|
556
|
+
- **Indentation**: 2 spaces
|
|
557
|
+
- **Line width**: 100 characters
|
|
558
|
+
- **Quotes**: Double quotes (`"`)
|
|
559
|
+
- **Semicolons**: Required
|
|
560
|
+
- **Trailing commas**: ES5 style
|
|
561
|
+
- **Line endings**: LF
|
|
562
|
+
|
|
563
|
+
### Naming Conventions
|
|
564
|
+
|
|
565
|
+
| Type | Convention | Example |
|
|
566
|
+
|------|------------|---------|
|
|
567
|
+
| Classes | PascalCase | `EnvManager` |
|
|
568
|
+
| Functions | camelCase | `getEnvString` |
|
|
569
|
+
| Variables | camelCase | `envValue` |
|
|
570
|
+
| Constants | SCREAMING_SNAKE_CASE | `DEFAULT_TIMEOUT` |
|
|
571
|
+
| Types/Interfaces | PascalCase | `EnvManagerOptions` |
|
|
572
|
+
| Files | dot-separated lowercase | `env.utils.ts` |
|
|
573
|
+
| Test files | `{name}.unit.test.ts` | `env-manager.unit.test.ts` |
|
|
574
|
+
|
|
575
|
+
### File Size Limits
|
|
576
|
+
|
|
577
|
+
- **Maximum 400 lines** per file
|
|
578
|
+
- Split large modules into smaller, focused files
|
|
579
|
+
|
|
580
|
+
### JSDoc and comments
|
|
581
|
+
|
|
582
|
+
- Prefer **edge cases and constraints** (e.g. empty path, encoding, sync vs async); avoid restating the function signature.
|
|
583
|
+
- Avoid long bullet lists for a single option; use one paragraph and link to README or examples.
|
|
584
|
+
- Move prose to README; keep examples minimal. Remove redundant `@file` / `@description` in modular code.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Subpath Exports
|
|
589
|
+
|
|
590
|
+
All packages SHOULD support subpath exports for tree-shaking:
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
// Main export (everything)
|
|
594
|
+
import { EnvManager, getEdgeString } from "@simpill/env.utils";
|
|
595
|
+
|
|
596
|
+
// Client-only (no fs dependencies)
|
|
597
|
+
import { getEdgeString } from "@simpill/env.utils/client";
|
|
598
|
+
|
|
599
|
+
// Server-only (full Node.js features)
|
|
600
|
+
import { EnvManager } from "@simpill/env.utils/server";
|
|
601
|
+
|
|
602
|
+
// Shared utilities
|
|
603
|
+
import { parseBoolean } from "@simpill/env.utils/shared";
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Entry Point Structure
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// src/index.ts - Main entry, re-exports everything
|
|
610
|
+
export * from "./client";
|
|
611
|
+
export * from "./server";
|
|
612
|
+
export * from "./shared";
|
|
613
|
+
|
|
614
|
+
// src/client/index.ts - Client exports
|
|
615
|
+
export { getEdgeString, getEdgeNumber } from "./env.edge";
|
|
616
|
+
|
|
617
|
+
// src/server/index.ts - Server exports
|
|
618
|
+
export { EnvManager } from "./env.utils";
|
|
619
|
+
|
|
620
|
+
// src/shared/index.ts - Shared exports
|
|
621
|
+
export { parseBoolean, parseNumber } from "./parse-helpers";
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## CI/CD Setup
|
|
627
|
+
|
|
628
|
+
Create GitHub Actions workflows in `.github/workflows/`:
|
|
629
|
+
|
|
630
|
+
### {name}-utils-ci.yml
|
|
631
|
+
|
|
632
|
+
```yaml
|
|
633
|
+
name: "{name}.utils CI"
|
|
634
|
+
|
|
635
|
+
on:
|
|
636
|
+
push:
|
|
637
|
+
branches: [main]
|
|
638
|
+
paths:
|
|
639
|
+
- "utils/@simpill-{name}.utils/**"
|
|
640
|
+
- ".github/workflows/{name}-utils-ci.yml"
|
|
641
|
+
pull_request:
|
|
642
|
+
branches: [main]
|
|
643
|
+
paths:
|
|
644
|
+
- "utils/@simpill-{name}.utils/**"
|
|
645
|
+
- ".github/workflows/{name}-utils-ci.yml"
|
|
646
|
+
|
|
647
|
+
defaults:
|
|
648
|
+
run:
|
|
649
|
+
working-directory: utils/@simpill-{name}.utils
|
|
650
|
+
|
|
651
|
+
jobs:
|
|
652
|
+
test:
|
|
653
|
+
runs-on: ubuntu-latest
|
|
654
|
+
strategy:
|
|
655
|
+
matrix:
|
|
656
|
+
node-version: [18.x, 20.x, 22.x]
|
|
657
|
+
|
|
658
|
+
steps:
|
|
659
|
+
- uses: actions/checkout@v4
|
|
660
|
+
|
|
661
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
662
|
+
uses: actions/setup-node@v4
|
|
663
|
+
with:
|
|
664
|
+
node-version: ${{ matrix.node-version }}
|
|
665
|
+
cache: "npm"
|
|
666
|
+
cache-dependency-path: "utils/@simpill-{name}.utils/package-lock.json"
|
|
667
|
+
|
|
668
|
+
- name: Install dependencies
|
|
669
|
+
run: npm ci
|
|
670
|
+
|
|
671
|
+
- name: Run linter
|
|
672
|
+
run: npm run lint
|
|
673
|
+
|
|
674
|
+
- name: Check formatting
|
|
675
|
+
run: npm run format:check
|
|
676
|
+
|
|
677
|
+
- name: Run type check
|
|
678
|
+
run: npx tsc --noEmit
|
|
679
|
+
|
|
680
|
+
- name: Run tests with coverage
|
|
681
|
+
run: npm run test:coverage
|
|
682
|
+
|
|
683
|
+
- name: Build
|
|
684
|
+
run: npm run build
|
|
685
|
+
|
|
686
|
+
- name: Upload coverage
|
|
687
|
+
uses: codecov/codecov-action@v4
|
|
688
|
+
with:
|
|
689
|
+
directory: utils/@simpill-{name}.utils/coverage
|
|
690
|
+
flags: {name}-utils
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## Checklist
|
|
696
|
+
|
|
697
|
+
Use this checklist when creating a new package:
|
|
698
|
+
|
|
699
|
+
### Initial Setup
|
|
700
|
+
|
|
701
|
+
- [ ] Create package directory: `utils/@simpill-{name}.utils/`
|
|
702
|
+
- [ ] Create `src/` directory structure (`client/`, `server/`, `shared/`)
|
|
703
|
+
- [ ] Create `__tests__/` directory structure (mirrors `src/`)
|
|
704
|
+
- [ ] Create `scripts/` directory with `check.sh`, `install-hooks.sh`, `pre-push.sh`
|
|
705
|
+
- [ ] Make scripts executable: `chmod +x scripts/*.sh`
|
|
706
|
+
|
|
707
|
+
### Configuration Files
|
|
708
|
+
|
|
709
|
+
- [ ] `package.json` with correct name, exports, and scripts
|
|
710
|
+
- [ ] `tsconfig.json` with strict mode
|
|
711
|
+
- [ ] `jest.config.js` with coverage thresholds
|
|
712
|
+
- [ ] `biome.json` with standard rules
|
|
713
|
+
- [ ] `.gitignore`
|
|
714
|
+
- [ ] `.npmignore`
|
|
715
|
+
|
|
716
|
+
### Documentation
|
|
717
|
+
|
|
718
|
+
- [ ] `README.md` with installation, usage, and API reference
|
|
719
|
+
- [ ] `AGENTS.md` with package-specific AI guidelines
|
|
720
|
+
- [ ] `CLAUDE.md` with Claude-specific instructions
|
|
721
|
+
|
|
722
|
+
### Source Code
|
|
723
|
+
|
|
724
|
+
- [ ] `src/index.ts` main entry point
|
|
725
|
+
- [ ] `src/client/index.ts` client exports
|
|
726
|
+
- [ ] `src/server/index.ts` server exports
|
|
727
|
+
- [ ] `src/shared/index.ts` shared exports
|
|
728
|
+
- [ ] Implementation files in appropriate directories
|
|
729
|
+
- [ ] No unbounded caches by default (document or use bounded cache/LRU)
|
|
730
|
+
- [ ] Timers and event listeners cleaned up in public APIs (e.g. destroy/close)
|
|
731
|
+
|
|
732
|
+
### Tests
|
|
733
|
+
|
|
734
|
+
- [ ] Unit tests for all public APIs
|
|
735
|
+
- [ ] Integration tests where applicable
|
|
736
|
+
- [ ] 80%+ coverage achieved
|
|
737
|
+
- [ ] All tests passing
|
|
738
|
+
|
|
739
|
+
### CI/CD
|
|
740
|
+
|
|
741
|
+
- [ ] `.github/workflows/{name}-utils-ci.yml`
|
|
742
|
+
- [ ] `.github/workflows/{name}-utils-release.yml` (optional)
|
|
743
|
+
|
|
744
|
+
### Final Verification
|
|
745
|
+
|
|
746
|
+
- [ ] `npm run verify` passes all checks
|
|
747
|
+
- [ ] `npm run build` produces valid output
|
|
748
|
+
- [ ] Package can be imported correctly
|
|
749
|
+
- [ ] Subpath exports work as expected
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## Example: Creating a New Package
|
|
754
|
+
|
|
755
|
+
```bash
|
|
756
|
+
# 1. Create directory structure (from repo root)
|
|
757
|
+
mkdir -p utils/@simpill-cache.utils/{src/{client,server,shared},__tests__/{client,server,shared}/{unit,integration},scripts}
|
|
758
|
+
|
|
759
|
+
# 2. Copy template files from env.utils
|
|
760
|
+
cp utils/@simpill-env.utils/package.json utils/@simpill-cache.utils/
|
|
761
|
+
cp utils/@simpill-env.utils/tsconfig.json utils/@simpill-cache.utils/
|
|
762
|
+
cp utils/@simpill-env.utils/jest.config.js utils/@simpill-cache.utils/
|
|
763
|
+
cp utils/@simpill-env.utils/biome.json utils/@simpill-cache.utils/
|
|
764
|
+
cp utils/@simpill-env.utils/.gitignore utils/@simpill-cache.utils/
|
|
765
|
+
cp utils/@simpill-env.utils/.npmignore utils/@simpill-cache.utils/
|
|
766
|
+
cp utils/@simpill-env.utils/scripts/*.sh utils/@simpill-cache.utils/scripts/
|
|
767
|
+
|
|
768
|
+
# 3. Update package.json
|
|
769
|
+
# - Change name to "@simpill/cache.utils"
|
|
770
|
+
# - Update description
|
|
771
|
+
# - Update keywords
|
|
772
|
+
# - Adjust dependencies
|
|
773
|
+
|
|
774
|
+
# 4. Create entry points
|
|
775
|
+
touch utils/@simpill-cache.utils/src/index.ts
|
|
776
|
+
touch utils/@simpill-cache.utils/src/client/index.ts
|
|
777
|
+
touch utils/@simpill-cache.utils/src/server/index.ts
|
|
778
|
+
touch utils/@simpill-cache.utils/src/shared/index.ts
|
|
779
|
+
|
|
780
|
+
# 5. Make scripts executable
|
|
781
|
+
chmod +x utils/@simpill-cache.utils/scripts/*.sh
|
|
782
|
+
|
|
783
|
+
# 6. Install dependencies
|
|
784
|
+
cd utils/@simpill-cache.utils && npm install
|
|
785
|
+
|
|
786
|
+
# 7. Start developing!
|
|
787
|
+
```
|