@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.
Files changed (46) hide show
  1. package/CONTRIBUTING.md +787 -0
  2. package/README.md +186 -0
  3. package/__tests__/README.md +32 -0
  4. package/__tests__/e2e/all-packages-resolve.e2e.test.ts +40 -0
  5. package/__tests__/integration/env-and-async.integration.test.ts +12 -0
  6. package/__tests__/integration/errors-and-uuid.integration.test.ts +14 -0
  7. package/__tests__/integration/object-and-array.integration.test.ts +15 -0
  8. package/__tests__/unit/@simpill/_resolver/resolve-packages.unit.test.ts +47 -0
  9. package/__tests__/unit/@simpill/array.utils/array.utils.unit.test.ts +11 -0
  10. package/__tests__/unit/@simpill/async.utils/async.utils.unit.test.ts +12 -0
  11. package/__tests__/unit/@simpill/cache.utils/cache.utils.unit.test.ts +21 -0
  12. package/__tests__/unit/@simpill/env.utils/env.utils.unit.test.ts +13 -0
  13. package/__tests__/unit/@simpill/errors.utils/errors.utils.unit.test.ts +13 -0
  14. package/__tests__/unit/@simpill/object.utils/object.utils.unit.test.ts +11 -0
  15. package/__tests__/unit/@simpill/patterns.utils/patterns.utils.unit.test.ts +23 -0
  16. package/__tests__/unit/@simpill/string.utils/string.utils.unit.test.ts +11 -0
  17. package/__tests__/unit/@simpill/time.utils/time.utils.unit.test.ts +12 -0
  18. package/__tests__/unit/@simpill/uuid.utils/uuid.utils.unit.test.ts +12 -0
  19. package/docs/PUBLISHING_AND_PACKAGES.md +258 -0
  20. package/docs/template/.env.sample +0 -0
  21. package/docs/template/README.md +0 -0
  22. package/docs/template/TEMPLATE.md +1040 -0
  23. package/docs/template/assets/logo-banner.svg +20 -0
  24. package/docs/template/package.json +14 -0
  25. package/index.ts +89 -0
  26. package/package.json +87 -0
  27. package/scripts/README.md +57 -0
  28. package/scripts/github/github-set-all-topics.js +120 -0
  29. package/scripts/github/github-set-repo-topics.sh +33 -0
  30. package/scripts/github/github-set-repos-public.sh +71 -0
  31. package/scripts/lib/package-topics.js +57 -0
  32. package/scripts/lib/publish-order.js +140 -0
  33. package/scripts/lib/sync-repo-links.js +75 -0
  34. package/scripts/monorepo/install-hooks.sh +64 -0
  35. package/scripts/monorepo/monorepo-clean.sh +7 -0
  36. package/scripts/monorepo/monorepo-sync-deps.js +81 -0
  37. package/scripts/monorepo/monorepo-verify-deps.js +37 -0
  38. package/scripts/monorepo/use-local-utils-at-root.js +49 -0
  39. package/scripts/publish/publish-all.sh +152 -0
  40. package/scripts/utils/utils-fix-repo-metadata.js +61 -0
  41. package/scripts/utils/utils-prepare-all.sh +107 -0
  42. package/scripts/utils/utils-set-npm-keywords.js +132 -0
  43. package/scripts/utils/utils-update-readme-badges.js +83 -0
  44. package/scripts/utils/utils-use-local-deps.js +43 -0
  45. package/scripts/utils/utils-verify-all.sh +45 -0
  46. package/tsconfig.json +14 -0
@@ -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
+ ```