|
94 | 94 | - Unknown commands log error to stderr and exit with code 1 |
95 | 95 | - Build and lint pass without errors |
96 | 96 |
|
| 97 | +### 2026-04-29: Version Management Pattern — Single Source of Truth |
| 98 | + |
| 99 | +**Problem:** Version was maintained in two places: `package.json` ("0.1.3-alpha.0") and hardcoded in `src/cli/index.ts` (".version('0.1.0')"). This caused version drift and required manual updates in both locations. |
| 100 | + |
| 101 | +**Solution:** Import version from `package.json` using ESM import attributes (Node 22+ with TypeScript): |
| 102 | +```typescript |
| 103 | +import packageJson from '../../package.json' with { type: 'json' }; |
| 104 | +program.version(packageJson.version); |
| 105 | +``` |
| 106 | + |
| 107 | +**Key Implementation Notes:** |
| 108 | +1. **Import syntax:** Use `with { type: 'json' }` (not `assert`) — TypeScript TS2880 error enforces the newer import attributes syntax |
| 109 | +2. **Path resolution:** From `src/cli/index.ts`, use `../../package.json` — when compiled to `dist/cli/index.js`, this resolves correctly to root `package.json` |
| 110 | +3. **tsconfig requirement:** `resolveJsonModule: true` (already configured) enables JSON imports in TypeScript |
| 111 | +4. **Node version:** Requires Node 22+ for import attributes support (already enforced via `"engines": {"node": ">=22.0.0"}`) |
| 112 | + |
| 113 | +**Benefits:** |
| 114 | +- Single source of truth: `package.json` version is the canonical version |
| 115 | +- Automated versioning: `npm version` updates package.json, CLI automatically reflects the change |
| 116 | +- Eliminates drift: No manual synchronization required between files |
| 117 | +- Standard pattern: Follows Node.js ecosystem conventions for CLI tools |
| 118 | + |
| 119 | +**Verification:** |
| 120 | +- Build passes: `npm run build` compiles successfully |
| 121 | +- Version output correct: `node dist/cli/index.js --version` displays "0.1.3-alpha.0" from package.json |
| 122 | +- No runtime dependencies: Uses native Node ESM features, no additional packages required |
| 123 | + |
| 124 | +### 2026-04-29: Dual-Mode Init — Public npm vs Local Tarball |
| 125 | + |
| 126 | +**Problem:** After publishing `@peterhauge/apiops-cli` to npm, `apiops init` still required `--cli-package <path>` pointing to a local .tgz tarball, making the workflow cumbersome for users who just want to use the public package. |
| 127 | + |
| 128 | +**Solution:** Made `--cli-package` optional and implemented two modes: |
| 129 | + |
| 130 | +1. **Local tarball mode** (when `--cli-package` provided): |
| 131 | + - Creates `.apiops/` directory, copies tarball |
| 132 | + - Generates package.json with `"apiops": "file:.apiops/{tarball}"` |
| 133 | + - Use case: Local development, pre-release testing |
| 134 | + |
| 135 | +2. **Public npm mode** (when `--cli-package` NOT provided): |
| 136 | + - No tarball copy, no `.apiops/` directory |
| 137 | + - Generates package.json with `"@peterhauge/apiops-cli": "latest"` |
| 138 | + - Use case: Standard consumption after publishing to npm |
| 139 | + |
| 140 | +**Implementation Details:** |
| 141 | +- Changed `.requiredOption()` to `.option()` in init-command.ts |
| 142 | +- Made `cliPackage?: string` optional in InitConfig interface |
| 143 | +- Conditional validation: `validateCliPackage()` only runs if `cliPackage` provided |
| 144 | +- Conditional file operations in `generateFiles()`: tarball copy and `.apiops/` creation only in local mode |
| 145 | +- Refactored package-json.ts to accept discriminated union config: `{ mode: 'local', tarballRelPath } | { mode: 'npm' }` |
| 146 | + |
| 147 | +**Key Pattern:** The package.json generator uses a discriminated union for type safety: |
| 148 | +```typescript |
| 149 | +export type PackageJsonConfig = |
| 150 | + | { mode: 'local'; tarballRelPath: string } |
| 151 | + | { mode: 'npm' }; |
| 152 | +``` |
| 153 | +This enforces that `tarballRelPath` is only accessible when `mode === 'local'`, preventing runtime errors. |
| 154 | + |
| 155 | +**Testing:** |
| 156 | +- All 467 tests pass (init-command.test.ts validates both modes) |
| 157 | +- ESLint clean (no warnings or errors) |
| 158 | +- Backward compatible: Existing workflows with `--cli-package` continue to work |
| 159 | + |
97 | 160 | <!-- Append new learnings here after each session --> |
98 | 161 |
|
0 commit comments