@zigrivers/scaffold 3.7.0 → 3.8.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 (58) hide show
  1. package/README.md +43 -3
  2. package/content/knowledge/library/library-api-design.md +306 -0
  3. package/content/knowledge/library/library-architecture.md +247 -0
  4. package/content/knowledge/library/library-bundling.md +244 -0
  5. package/content/knowledge/library/library-conventions.md +229 -0
  6. package/content/knowledge/library/library-dev-environment.md +220 -0
  7. package/content/knowledge/library/library-documentation.md +300 -0
  8. package/content/knowledge/library/library-project-structure.md +237 -0
  9. package/content/knowledge/library/library-requirements.md +173 -0
  10. package/content/knowledge/library/library-security.md +257 -0
  11. package/content/knowledge/library/library-testing.md +319 -0
  12. package/content/knowledge/library/library-type-definitions.md +284 -0
  13. package/content/knowledge/library/library-versioning.md +300 -0
  14. package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
  15. package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
  16. package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
  17. package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
  18. package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
  19. package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
  20. package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
  21. package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
  22. package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
  23. package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
  24. package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
  25. package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
  26. package/content/methodology/library-overlay.yml +67 -0
  27. package/content/methodology/mobile-app-overlay.yml +71 -0
  28. package/dist/cli/commands/init.d.ts +9 -0
  29. package/dist/cli/commands/init.d.ts.map +1 -1
  30. package/dist/cli/commands/init.js +82 -3
  31. package/dist/cli/commands/init.js.map +1 -1
  32. package/dist/cli/commands/init.test.js +70 -0
  33. package/dist/cli/commands/init.test.js.map +1 -1
  34. package/dist/config/schema.d.ts +592 -32
  35. package/dist/config/schema.d.ts.map +1 -1
  36. package/dist/config/schema.js +34 -0
  37. package/dist/config/schema.js.map +1 -1
  38. package/dist/config/schema.test.js +147 -1
  39. package/dist/config/schema.test.js.map +1 -1
  40. package/dist/core/assembly/overlay-loader.test.js +22 -0
  41. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  42. package/dist/e2e/project-type-overlays.test.d.ts +2 -1
  43. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -1
  44. package/dist/e2e/project-type-overlays.test.js +302 -2
  45. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  46. package/dist/types/config.d.ts +7 -1
  47. package/dist/types/config.d.ts.map +1 -1
  48. package/dist/wizard/questions.d.ts +12 -1
  49. package/dist/wizard/questions.d.ts.map +1 -1
  50. package/dist/wizard/questions.js +56 -1
  51. package/dist/wizard/questions.js.map +1 -1
  52. package/dist/wizard/questions.test.js +89 -4
  53. package/dist/wizard/questions.test.js.map +1 -1
  54. package/dist/wizard/wizard.d.ts +9 -0
  55. package/dist/wizard/wizard.d.ts.map +1 -1
  56. package/dist/wizard/wizard.js +12 -1
  57. package/dist/wizard/wizard.js.map +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,244 @@
1
+ ---
2
+ name: library-bundling
3
+ description: ESM/CJS dual publishing, package.json exports map, bundler configuration, and tree-shaking verification for libraries
4
+ topics: [library, bundling, esm, cjs, dual-publishing, exports-map, tree-shaking, tsup, rollup]
5
+ ---
6
+
7
+ Library bundling solves the problem of serving multiple module systems from one codebase. The JavaScript ecosystem is mid-transition from CommonJS to ES modules, and libraries must serve both until the transition completes. Getting bundling wrong produces libraries that fail to import in certain environments, cause dual-package hazards (two instances of the same library loaded simultaneously), or defeat tree-shaking and inflate consumer bundle sizes.
8
+
9
+ ## Summary
10
+
11
+ Use a bundler (tsup or rollup) rather than raw TypeScript compilation for libraries that need dual ESM/CJS output. The `package.json` exports map is the canonical module resolution contract — define it precisely with condition precedence (types before default, import before require). Set `"sideEffects": false` when true to enable aggressive tree-shaking. Test module resolution in real consumer environments, not just in your build output. ESM-only is acceptable if your minimum supported environment supports it; document this clearly.
12
+
13
+ Key bundling decisions:
14
+ - Output formats: ESM + CJS for maximum compatibility; ESM-only for modern toolchains
15
+ - File extensions: `.js`/`.cjs` to signal format explicitly
16
+ - Declaration files: emitted alongside each output, not separately
17
+ - Exports map: precise condition ordering (types, import, require, default)
18
+ - Tree-shaking: `sideEffects: false` + ES module output + no barrel-file anti-patterns
19
+
20
+ ## Deep Guidance
21
+
22
+ ### Choosing a Bundler
23
+
24
+ **tsup (recommended for most libraries):**
25
+ tsup is a TypeScript-first bundler built on esbuild. Fast, opinionated, handles dual ESM/CJS output with declaration files:
26
+
27
+ ```typescript
28
+ // tsup.config.ts
29
+ import { defineConfig } from 'tsup'
30
+
31
+ export default defineConfig({
32
+ entry: ['src/index.ts'],
33
+ format: ['esm', 'cjs'],
34
+ dts: true, // Emit .d.ts declaration files
35
+ sourcemap: true,
36
+ clean: true, // Clean dist/ before each build
37
+ splitting: false, // Keep single output file per format
38
+ treeshake: true,
39
+ outExtension({ format }) {
40
+ return {
41
+ js: format === 'cjs' ? '.cjs' : '.js'
42
+ }
43
+ }
44
+ })
45
+ ```
46
+
47
+ Output:
48
+ ```
49
+ dist/
50
+ ├── index.js # ESM
51
+ ├── index.cjs # CJS
52
+ ├── index.d.ts # TypeScript declarations
53
+ └── index.d.cts # CJS declarations (tsup generates automatically)
54
+ ```
55
+
56
+ **rollup (when you need fine-grained control):**
57
+ ```javascript
58
+ // rollup.config.mjs
59
+ import typescript from '@rollup/plugin-typescript'
60
+ import { nodeResolve } from '@rollup/plugin-node-resolve'
61
+
62
+ export default [
63
+ {
64
+ input: 'src/index.ts',
65
+ output: { file: 'dist/index.js', format: 'es', sourcemap: true },
66
+ plugins: [nodeResolve(), typescript({ declaration: false })]
67
+ },
68
+ {
69
+ input: 'src/index.ts',
70
+ output: { file: 'dist/index.cjs', format: 'cjs', sourcemap: true, exports: 'named' },
71
+ plugins: [nodeResolve(), typescript({ declaration: false })]
72
+ }
73
+ ]
74
+ ```
75
+
76
+ **tsc only (when bundling is unnecessary):**
77
+ If the library has no dependencies to bundle, raw `tsc` with separate CJS and ESM configs works. Use this for pure type libraries or libraries where consumers handle bundling:
78
+
79
+ ```bash
80
+ # ESM
81
+ tsc -p tsconfig.json
82
+
83
+ # CJS (separate tsconfig)
84
+ tsc -p tsconfig.cjs.json
85
+ ```
86
+
87
+ ### Exports Map Configuration
88
+
89
+ The `exports` field in `package.json` is the definitive module resolution spec for Node.js 12+ and modern bundlers. Define it precisely:
90
+
91
+ ```json
92
+ {
93
+ "exports": {
94
+ ".": {
95
+ "import": {
96
+ "types": "./dist/index.d.ts",
97
+ "default": "./dist/index.js"
98
+ },
99
+ "require": {
100
+ "types": "./dist/index.d.cts",
101
+ "default": "./dist/index.cjs"
102
+ }
103
+ },
104
+ "./plugins": {
105
+ "import": {
106
+ "types": "./dist/plugins/index.d.ts",
107
+ "default": "./dist/plugins/index.js"
108
+ },
109
+ "require": {
110
+ "types": "./dist/plugins/index.d.cts",
111
+ "default": "./dist/plugins/index.cjs"
112
+ }
113
+ },
114
+ "./package.json": "./package.json"
115
+ }
116
+ }
117
+ ```
118
+
119
+ **Condition ordering matters:**
120
+ - `types` must come before `default` so TypeScript resolves declarations correctly
121
+ - `import` before `require` (ESM preferred when both are available)
122
+ - `default` as the final fallback
123
+
124
+ **Legacy fields for older tooling:**
125
+ Keep `"main"` and `"module"` for older bundlers and Node versions that don't support `exports`:
126
+ ```json
127
+ {
128
+ "main": "./dist/index.cjs",
129
+ "module": "./dist/index.js",
130
+ "types": "./dist/index.d.ts"
131
+ }
132
+ ```
133
+
134
+ Bundlers like webpack 4 and older rollup configurations use `"module"` for ESM. Modern tooling uses `exports`.
135
+
136
+ ### Dual Package Hazard
137
+
138
+ The dual package hazard occurs when both ESM and CJS versions of the same library are loaded in the same process, creating two instances of what should be a singleton. Symptoms: `instanceof` checks fail, shared state doesn't sync, plugin registrations disappear.
139
+
140
+ **Prevention strategies:**
141
+
142
+ 1. **State in the ESM version only:**
143
+ ```javascript
144
+ // dist/index.cjs — CJS wrapper that re-exports the ESM version
145
+ // This ensures only one module instance regardless of import style
146
+ const mod = await import('./index.js')
147
+ module.exports = mod
148
+ ```
149
+
150
+ 2. **Stateless library design (best):**
151
+ Design the library with no module-level state. Factory functions create instances; there is no singleton. With no shared state, dual loading is harmless:
152
+ ```typescript
153
+ // NO module-level state — safe for dual loading
154
+ export function createCache(): Cache { return new Map() }
155
+ export function parseConfig(input: string): Config { /* pure function */ }
156
+ ```
157
+
158
+ 3. **Wrapper CJS file:**
159
+ ```javascript
160
+ // dist/index.cjs — thin CJS wrapper
161
+ 'use strict'
162
+ const mod = require('./index.js') // This won't work if index.js is ESM
163
+ // Use a proper wrapper instead:
164
+ Object.assign(exports, require('./index-cjs-impl.cjs'))
165
+ ```
166
+
167
+ For complex libraries with state, use approach 1 or design as approach 2.
168
+
169
+ ### Tree-Shaking Verification
170
+
171
+ After building, verify that tree-shaking actually works:
172
+
173
+ ```bash
174
+ # Install a fresh consumer project and import one function
175
+ mkdir /tmp/tree-shake-test && cd /tmp/tree-shake-test
176
+ npm init -y
177
+ npm install my-library@file:/path/to/library
178
+
179
+ cat > index.js << 'EOF'
180
+ import { parseConfig } from 'my-library'
181
+ const config = parseConfig('[server]\nhost = "localhost"')
182
+ console.log(config)
183
+ EOF
184
+
185
+ # Bundle with rollup and check output size
186
+ npx rollup index.js --format iife --bundle > bundle.js
187
+ wc -c bundle.js
188
+
189
+ # If the bundle is larger than just parseConfig + its dependencies,
190
+ # tree-shaking is not working — investigate the sideEffects field
191
+ # and ES module output
192
+ ```
193
+
194
+ **Common tree-shaking failures:**
195
+ - CommonJS output used (bundler can't statically analyze)
196
+ - `sideEffects: true` in package.json (prevents dead code elimination)
197
+ - Barrel files that import everything (forces all code into bundle)
198
+ - `export * from './large-module'` at root when consumers only use one export
199
+
200
+ **Subpath exports enable opt-in tree-shaking at the feature level:**
201
+ ```typescript
202
+ // Consumer only needs the validator — zero parser code in bundle
203
+ import { validateSchema } from 'my-library/validators'
204
+ ```
205
+
206
+ ### Bundle Size Budgets
207
+
208
+ Define a bundle size budget for browser-targeted libraries:
209
+
210
+ ```json
211
+ // package.json
212
+ {
213
+ "size-limit": [
214
+ {
215
+ "path": "./dist/index.js",
216
+ "limit": "10 kB"
217
+ },
218
+ {
219
+ "path": "./dist/plugins/index.js",
220
+ "limit": "5 kB"
221
+ }
222
+ ]
223
+ }
224
+ ```
225
+
226
+ ```bash
227
+ # Check with size-limit
228
+ npx size-limit
229
+ ```
230
+
231
+ Enforce in CI: if a PR increases bundle size beyond the budget, fail the check. This prevents gradual size bloat.
232
+
233
+ ### Source Maps
234
+
235
+ Always emit source maps. They enable consumers to debug into the library source when troubleshooting:
236
+
237
+ ```typescript
238
+ // tsup.config.ts
239
+ export default defineConfig({
240
+ sourcemap: true, // Emits .js.map alongside .js
241
+ })
242
+ ```
243
+
244
+ Source maps should be included in the published package (`dist/*.map`). They don't significantly affect install size but dramatically improve debugging.
@@ -0,0 +1,229 @@
1
+ ---
2
+ name: library-conventions
3
+ description: Public API naming, deprecation patterns, changelog conventions, and export patterns for published libraries
4
+ topics: [library, conventions, naming, deprecation, changelog, exports, api-design]
5
+ ---
6
+
7
+ Library conventions are the agreements that make a library predictable, navigable, and trustworthy across versions. They cover how public APIs are named, how deprecated APIs are marked and eventually removed, how changes are communicated through changelogs, and how exports are structured. Inconsistent conventions are a tax on every consumer — they create confusion about what is stable, what is safe to use, and what changed between versions.
8
+
9
+ ## Summary
10
+
11
+ Establish and document conventions before publishing v1. Key areas: consistent naming patterns (verbs for functions, nouns for types/classes, `is`/`has` for predicates), deprecation lifecycle with JSDoc markers and migration guidance, changelog format (Keep a Changelog or Conventional Commits), and export structure that makes tree-shaking possible. Internal exports must be clearly separated from public exports to avoid accidental API surface expansion.
12
+
13
+ Core conventions:
14
+ - Functions: verb-noun (`parseConfig`, `validateSchema`, `createClient`)
15
+ - Types/interfaces: PascalCase nouns (`ParseOptions`, `ClientConfig`, `ValidationError`)
16
+ - Predicates: `is` prefix (`isError`, `isValidConfig`)
17
+ - Constants: SCREAMING_SNAKE for module-level, camelCase for config object keys
18
+ - Deprecation: JSDoc `@deprecated` + replacement reference + removal version
19
+ - Changelog: per-version sections with Added / Changed / Deprecated / Removed / Fixed / Security
20
+
21
+ ## Deep Guidance
22
+
23
+ ### Public API Naming
24
+
25
+ Naming is the most visible part of the API contract. Inconsistency in naming signals immaturity and creates cognitive load for consumers.
26
+
27
+ **Functions — verb-noun pattern:**
28
+ ```typescript
29
+ // Good: verb-noun, action is clear
30
+ parseConfig(input: string): Config
31
+ validateSchema(schema: Schema): ValidationResult
32
+ createClient(options: ClientOptions): Client
33
+ formatError(error: unknown): string
34
+ resolveModulePath(specifier: string): string
35
+
36
+ // Bad: ambiguous, no verb, or inverted
37
+ config(input: string): Config // what does it do?
38
+ schemaValidator(schema: Schema): ... // -or suffix is not a function verb
39
+ client(options: ClientOptions): ... // noun-only looks like a constructor
40
+ ```
41
+
42
+ **Types and interfaces — PascalCase nouns:**
43
+ ```typescript
44
+ // Good
45
+ interface ParseOptions { ... }
46
+ type ValidationResult = { valid: boolean; errors: ValidationError[] }
47
+ class ConfigClient { ... }
48
+ type ErrorCode = 'NOT_FOUND' | 'INVALID' | 'TIMEOUT'
49
+
50
+ // Bad
51
+ interface parseOptions { ... } // lowercase
52
+ type validation_result = ... // snake_case
53
+ type ErrCode = ... // abbreviation
54
+ ```
55
+
56
+ **Predicates — `is` prefix:**
57
+ ```typescript
58
+ function isError(value: unknown): value is Error
59
+ function isValidConfig(config: unknown): config is Config
60
+ function hasRequiredFields(obj: unknown): boolean // 'has' for possession checks
61
+ ```
62
+
63
+ **Boolean options — avoid double negatives:**
64
+ ```typescript
65
+ // Good
66
+ interface Options {
67
+ strict: boolean // enable strict mode
68
+ cache: boolean // enable caching
69
+ }
70
+
71
+ // Bad
72
+ interface Options {
73
+ noStrict: boolean // double negative when true disables
74
+ disableCache: boolean // confusing when combined: disableCache: false
75
+ }
76
+ ```
77
+
78
+ **Error types — descriptive, namespace-prefixed:**
79
+ ```typescript
80
+ // Good: namespaced, descriptive
81
+ class ParseError extends Error {
82
+ constructor(message: string, public readonly line: number, public readonly col: number) {
83
+ super(message)
84
+ this.name = 'ParseError'
85
+ }
86
+ }
87
+
88
+ // Available as named export:
89
+ export { ParseError, ValidationError, NetworkError, TimeoutError }
90
+ ```
91
+
92
+ ### Deprecation Lifecycle
93
+
94
+ Deprecation is a promise to consumers: "this still works today, but plan to migrate." It must be communicated at multiple levels.
95
+
96
+ **Step 1: Add `@deprecated` JSDoc in a MINOR release:**
97
+ ```typescript
98
+ /**
99
+ * Parse a configuration string.
100
+ * @deprecated Use `parseConfig()` instead. Will be removed in v3.0.
101
+ * @see parseConfig
102
+ */
103
+ export function parse(input: string): Config {
104
+ return parseConfig(input)
105
+ }
106
+ ```
107
+
108
+ The `@deprecated` tag causes TypeScript to show strikethrough in IDEs and emit warnings. Always include:
109
+ - What to use instead
110
+ - When it will be removed (target major version)
111
+
112
+ **Step 2: Log a runtime warning (optional, for JS users without TypeScript):**
113
+ ```typescript
114
+ export function parse(input: string): Config {
115
+ if (process.env.NODE_ENV !== 'production') {
116
+ console.warn(
117
+ '[my-library] parse() is deprecated. Use parseConfig() instead. ' +
118
+ 'Will be removed in v3.0. See migration guide: https://example.com/v3-migration'
119
+ )
120
+ }
121
+ return parseConfig(input)
122
+ }
123
+ ```
124
+
125
+ Only add runtime warnings if the library has significant JS (non-TypeScript) consumers. Don't pollute production logs.
126
+
127
+ **Step 3: Remove in the next major version:**
128
+ - Remove the export entirely
129
+ - Add a clear CHANGELOG entry with migration instructions
130
+ - Include migration guide link in the changelog entry
131
+
132
+ **Deprecation period policy:**
133
+ The minimum deprecation period before removal should be one full major version. If you deprecate in v2.3, the earliest removal is v3.0. Communicate the removal version at deprecation time.
134
+
135
+ ### Changelog Conventions
136
+
137
+ Follow the [Keep a Changelog](https://keepachangelog.com) format. Every release must have a changelog entry before publishing.
138
+
139
+ **Format:**
140
+ ```markdown
141
+ # Changelog
142
+
143
+ ## [Unreleased]
144
+
145
+ ## [2.1.0] - 2024-03-15
146
+
147
+ ### Added
148
+ - `parseConfig()` function as the new primary parsing API
149
+ - `ParseOptions.strict` flag for strict mode validation
150
+
151
+ ### Changed
152
+ - `createClient()` now accepts `ClientOptions.timeout` in milliseconds (previously seconds)
153
+
154
+ ### Deprecated
155
+ - `parse()` — use `parseConfig()` instead. Will be removed in v3.0.
156
+
157
+ ### Fixed
158
+ - `validateSchema()` no longer throws on empty input; returns `{ valid: false, errors: [] }`
159
+
160
+ ## [2.0.0] - 2024-01-10
161
+
162
+ ### Breaking Changes
163
+ - Removed `connect()` (deprecated since v1.5.0). Use `createClient()`.
164
+ - `Config.timeout` is now in milliseconds (was seconds in v1.x). Multiply existing values by 1000.
165
+
166
+ ### Migration from v1.x
167
+ See: https://example.com/v2-migration
168
+ ```
169
+
170
+ Rules:
171
+ - Every entry in "Breaking Changes" must have a migration instruction or link
172
+ - "Added" entries must reference the new API by name
173
+ - "Fixed" entries must describe the incorrect behavior and the correct behavior
174
+ - Never put vague entries like "Various bug fixes" — enumerate them
175
+
176
+ ### Export Patterns
177
+
178
+ How you structure exports determines your tree-shaking story and your public API surface.
179
+
180
+ **Root index.ts — explicit, intentional exports only:**
181
+ ```typescript
182
+ // src/index.ts
183
+ // Public API — these are the semver-protected exports
184
+
185
+ // Core functions
186
+ export { parseConfig } from './parser'
187
+ export { validateSchema } from './validator'
188
+ export { createClient } from './client'
189
+
190
+ // Types
191
+ export type { ParseOptions, ParseResult } from './parser'
192
+ export type { ValidationResult, ValidationError } from './validator'
193
+ export type { ClientOptions, Client } from './client'
194
+
195
+ // Error types
196
+ export { ParseError, ValidationError as LibValidationError } from './errors'
197
+
198
+ // DO NOT export internal utilities
199
+ // DO NOT re-export everything with `export * from './...'`
200
+ ```
201
+
202
+ **Avoid `export *` at the root:** It makes the API surface opaque and causes accidental exports of internal symbols.
203
+
204
+ **Subpath exports for optional features:**
205
+ ```typescript
206
+ // package.json exports map (see library-bundling.md for full config)
207
+ {
208
+ "exports": {
209
+ ".": "./dist/index.js",
210
+ "./plugins": "./dist/plugins/index.js",
211
+ "./testing": "./dist/testing/index.js"
212
+ }
213
+ }
214
+ ```
215
+
216
+ Consumers who don't use plugins pay zero bundle cost. Testing utilities stay separate from production code.
217
+
218
+ **Barrel files — use sparingly:**
219
+ Barrel files (files that re-export from many modules) can defeat tree-shaking in some bundlers. Prefer deep imports in internal code; use the root barrel only for the public API.
220
+
221
+ ### Convention Documentation
222
+
223
+ Every library must have a `CONTRIBUTING.md` or `docs/conventions.md` documenting:
224
+ 1. Naming conventions for new API additions
225
+ 2. Deprecation lifecycle steps (checklist format)
226
+ 3. Changelog update requirement (must update before PR merges)
227
+ 4. Export checklist for new public APIs
228
+
229
+ Without documented conventions, contributors add APIs inconsistently, and the library accumulates naming debt that is expensive to fix without breaking changes.
@@ -0,0 +1,220 @@
1
+ ---
2
+ name: library-dev-environment
3
+ description: Monorepo setup, npm link workflow, build watch mode, and local consumer testing for library development
4
+ topics: [library, dev-environment, monorepo, npm-link, build-watch, local-testing]
5
+ ---
6
+
7
+ Library development environment setup is distinct from application development: you are building code that will be consumed by other projects, which means your dev workflow must include a way to test the library as a consumer would — before publishing to npm. The feedback loop between changing library source and seeing the effect in a consumer application is the central challenge. Get this wrong, and you spend hours debugging issues that only surface after publish.
8
+
9
+ ## Summary
10
+
11
+ Use build watch mode (TypeScript `--watch` or a bundler watcher) for fast feedback during development. For testing in a real consumer project, use `npm link` or workspace-relative `file:` references. In monorepos, use npm/pnpm/yarn workspaces to co-locate the library and consumer apps. Never develop library code exclusively through unit tests — always validate through a real consumer context. Set up scripts for the full dev loop: `build:watch` in one terminal, consumer app in another.
12
+
13
+ Core workflow tools:
14
+ - `tsc --watch` for TypeScript compilation feedback
15
+ - `npm link` / `pnpm link` for local cross-project testing
16
+ - Workspace `file:` references for monorepo consumers
17
+ - `npm pack` + install for pre-publish verification
18
+
19
+ ## Deep Guidance
20
+
21
+ ### Build Watch Mode
22
+
23
+ The development feedback loop starts with build watch mode. TypeScript's `--watch` mode recompiles on every save:
24
+
25
+ ```bash
26
+ # Terminal 1: watch the library build
27
+ npm run build:watch
28
+
29
+ # package.json script:
30
+ "build:watch": "tsc -p tsconfig.json --watch --preserveWatchOutput"
31
+ ```
32
+
33
+ For more complex builds (bundling, multiple outputs), use a bundler watcher:
34
+
35
+ ```bash
36
+ # With tsup (recommended for dual ESM/CJS builds)
37
+ "build:watch": "tsup --watch"
38
+
39
+ # tsup.config.ts
40
+ import { defineConfig } from 'tsup'
41
+
42
+ export default defineConfig({
43
+ entry: ['src/index.ts'],
44
+ format: ['esm', 'cjs'],
45
+ dts: true,
46
+ sourcemap: true,
47
+ clean: true,
48
+ watch: process.env.WATCH === 'true'
49
+ })
50
+ ```
51
+
52
+ TypeScript `--watch` alone is sufficient for type-only changes. If you're bundling (minifying, inlining), use the bundler's watch mode.
53
+
54
+ ### npm link Workflow
55
+
56
+ `npm link` creates a symlink from your global npm prefix to the library, then links that into the consuming project:
57
+
58
+ ```bash
59
+ # In the library directory:
60
+ cd my-library
61
+ npm link
62
+ # Creates: ~/.nvm/versions/node/vX/lib/node_modules/my-library -> /path/to/my-library
63
+
64
+ # In the consuming project:
65
+ cd my-app
66
+ npm link my-library
67
+ # Creates: my-app/node_modules/my-library -> ~/.nvm/.../my-library
68
+
69
+ # The consumer now uses the live dist/ from the library
70
+ ```
71
+
72
+ **Caveats with npm link:**
73
+ - The consumer uses the `dist/` directory, so the library must be built first and kept rebuilt via watch mode
74
+ - React and other singleton libraries can cause issues because the library and consumer may each resolve their own copy: use `npm link my-app/node_modules/react` inside the library to force shared resolution
75
+ - `npm install` in the consumer will break the link — you must re-run `npm link my-library`
76
+
77
+ **Preferred alternative: `file:` reference in consumer:**
78
+ ```json
79
+ // my-app/package.json
80
+ {
81
+ "dependencies": {
82
+ "my-library": "file:../my-library"
83
+ }
84
+ }
85
+ ```
86
+
87
+ Run `npm install` in `my-app`. This creates a symlink into `my-library/` respecting the `exports` map. Survives `npm install` (unlike `npm link`). Requires the library to have its `dist/` built.
88
+
89
+ ### pnpm Workspace Setup (Recommended for Monorepos)
90
+
91
+ pnpm workspaces handle library + consumer in the same repository without symlink complexity:
92
+
93
+ ```yaml
94
+ # pnpm-workspace.yaml (at monorepo root)
95
+ packages:
96
+ - 'packages/*'
97
+ - 'apps/*'
98
+ - 'examples/*'
99
+ ```
100
+
101
+ ```
102
+ monorepo/
103
+ ├── packages/
104
+ │ └── my-library/
105
+ │ ├── src/
106
+ │ ├── dist/
107
+ │ └── package.json # name: "my-library"
108
+ ├── apps/
109
+ │ └── my-app/
110
+ │ └── package.json # depends on "my-library": "workspace:*"
111
+ └── pnpm-workspace.yaml
112
+ ```
113
+
114
+ ```json
115
+ // apps/my-app/package.json
116
+ {
117
+ "dependencies": {
118
+ "my-library": "workspace:*"
119
+ }
120
+ }
121
+ ```
122
+
123
+ With `workspace:*`, pnpm links to the local package automatically. The `dist/` directory is used (respecting `exports` map), so the library still needs to be built.
124
+
125
+ **Monorepo dev script:**
126
+ ```bash
127
+ # Run both library watch and app dev server in parallel
128
+ "dev": "concurrently \"npm run build:watch -w packages/my-library\" \"npm run dev -w apps/my-app\""
129
+ ```
130
+
131
+ ### Pre-publish Verification with npm pack
132
+
133
+ Before publishing, verify the actual package contents:
134
+
135
+ ```bash
136
+ # Pack the library without publishing
137
+ npm pack --dry-run
138
+
139
+ # This shows exactly what files will be included in the published package
140
+ # Look for:
141
+ # - dist/ files present (ESM, CJS, .d.ts)
142
+ # - No src/ files (source not published)
143
+ # - No test files
144
+ # - README.md and CHANGELOG.md present
145
+ # - No .env or secrets
146
+
147
+ # Pack to a tarball and install it in a test project
148
+ npm pack
149
+ # Creates: my-library-1.0.0.tgz
150
+
151
+ # In a fresh test project:
152
+ npm install ../my-library/my-library-1.0.0.tgz
153
+ ```
154
+
155
+ Installing the tarball is the most faithful pre-publish test. It reproduces exactly what consumers get from `npm install my-library`.
156
+
157
+ ### Dev Dependencies vs. Build Dependencies
158
+
159
+ Keep the dev environment fast by understanding what belongs where:
160
+
161
+ **devDependencies** (not published):
162
+ ```json
163
+ {
164
+ "devDependencies": {
165
+ "typescript": "^5.4.0", // Build tool
166
+ "tsup": "^8.0.0", // Bundler
167
+ "vitest": "^1.4.0", // Test runner
168
+ "tsd": "^0.31.0", // Type testing
169
+ "typedoc": "^0.25.0", // Doc generation
170
+ "eslint": "^8.57.0", // Linter
171
+ "prettier": "^3.2.0", // Formatter
172
+ "concurrently": "^8.2.0", // Parallel scripts
173
+ "rimraf": "^5.0.0" // Cross-platform rm -rf
174
+ }
175
+ }
176
+ ```
177
+
178
+ **dependencies** (installed by consumers):
179
+ Only runtime dependencies that the library code imports at runtime. Keep this list minimal. Every dependency you add becomes a consumer's dependency. Prefer zero runtime dependencies for utility libraries.
180
+
181
+ **peerDependencies**:
182
+ Framework dependencies the consumer is expected to provide (React, Vue, etc.).
183
+
184
+ ### Environment Variables for Dev
185
+
186
+ Libraries should not read environment variables at runtime (that's the consumer's responsibility). But the build process may need them:
187
+
188
+ ```bash
189
+ # .env.local (gitignored) — only for build/test scripts
190
+ NPM_REGISTRY=https://registry.npmjs.org
191
+ TYPEDOC_TOKEN=... # for doc deployment
192
+ ```
193
+
194
+ Document any required environment variables in `docs/dev-setup.md`. Never hardcode registry URLs or tokens.
195
+
196
+ ### Recommended package.json Dev Scripts
197
+
198
+ ```json
199
+ {
200
+ "scripts": {
201
+ "build": "rimraf dist && tsup",
202
+ "build:watch": "tsup --watch",
203
+ "dev": "npm run build:watch",
204
+ "test": "vitest run",
205
+ "test:watch": "vitest",
206
+ "test:types": "tsd",
207
+ "test:coverage": "vitest run --coverage",
208
+ "test:examples": "node examples/basic-usage/index.js",
209
+ "lint": "eslint src/ tests/",
210
+ "format": "prettier --write src/ tests/",
211
+ "typecheck": "tsc --noEmit -p tsconfig.dev.json",
212
+ "docs": "typedoc",
213
+ "pack:dry": "npm pack --dry-run",
214
+ "prepublishOnly": "npm run build && npm run test && npm run test:types",
215
+ "clean": "rimraf dist"
216
+ }
217
+ }
218
+ ```
219
+
220
+ The `prepublishOnly` script is a safety net — it runs automatically before `npm publish` and blocks publishing if tests fail.