Skip to content

Commit 84f7ed6

Browse files
zrosenbauerclaude
andauthored
Refactor/fix standards violations (#43)
* chore: fix command export default issue * refactor(packages/core,packages/cli): fix coding standards violations Replace `as` type assertions with type annotations, type guards, and documented exceptions. Replace `try/catch` blocks with `attempt` and `attemptAsync` from es-toolkit. Replace multi-branch if/else chains with ts-pattern `match` expressions. Rename `redactPaths` to `REDACT_PATHS`. Document intentional mutation and throw exceptions. Closes #12, #15, #18, #20, #21, #33 Co-Authored-By: Claude <noreply@anthropic.com> * chore: missing work --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7042b46 commit 84f7ed6

File tree

26 files changed

+160
-152
lines changed

26 files changed

+160
-152
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ You are a strict functional programmer. You write pure, immutable, declarative T
2929
| [tsdown](https://tsdown.dev) | Bundler | [llms.txt](https://tsdown.dev/llms.txt) \| [llms-full.txt](https://tsdown.dev/llms-full.txt) |
3030
| [OXC](https://oxc.rs) (oxlint) | Linting | [llms.txt](https://oxc.rs/llms.txt) |
3131
| [Vitest](https://vitest.dev) | Testing | [GitHub](https://github.com/vitest-dev/vitest) |
32-
| [type-fest](https://github.com/sindresorhus/type-fest) | Type utilities | [GitHub](https://github.com/sindresorhus/type-fest) |
32+
| [type-fest](https://github.com/sindresorhus/type-fest) | Type utilities | [GitHub](https://github.com/sindresorhus/type-fest) |
3333
| [Changesets](https://github.com/changesets/changesets) | Versioning & publishing | [GitHub](https://github.com/changesets/changesets) |
3434

3535
## Commands

MIDDLEWARE.md

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ const loadUser = middleware<{ Variables: { user: User } }>(async (ctx, next) =>
148148
export default command({
149149
middleware: [loadUser],
150150
handler: async (ctx) => {
151-
ctx.user // User -- type-safe, scoped to this command
152-
ctx.user.role // 'admin' | 'user'
151+
ctx.user // User -- type-safe, scoped to this command
152+
ctx.user.role // 'admin' | 'user'
153153
},
154154
})
155155
```
@@ -226,10 +226,7 @@ http({
226226
```ts
227227
function compose(...middlewares: readonly Middleware[]): Middleware {
228228
return middleware(async (ctx, next) => {
229-
const chain = middlewares.reduceRight(
230-
(nextFn, mw) => () => mw.handler(ctx, nextFn),
231-
next,
232-
)
229+
const chain = middlewares.reduceRight((nextFn, mw) => () => mw.handler(ctx, nextFn), next)
233230
await chain()
234231
})
235232
}
@@ -252,47 +249,46 @@ auth({
252249
})
253250

254251
// After -- root middleware
255-
auth({
252+
;(auth({
256253
strategies: [
257254
auth.env(),
258255
auth.file(),
259256
auth.oauth({ clientId: '...', authUrl: '...', tokenUrl: '...' }),
260257
auth.token(),
261258
],
262259
}),
263-
http({
264-
baseUrl: 'https://api.example.com',
265-
namespace: 'api',
266-
headers: auth.headers(),
267-
}),
268-
269-
// After -- login command (no change for simple case)
270-
await ctx.auth.login()
260+
http({
261+
baseUrl: 'https://api.example.com',
262+
namespace: 'api',
263+
headers: auth.headers(),
264+
}),
265+
// After -- login command (no change for simple case)
266+
await ctx.auth.login())
271267
```
272268

273-
| Concern | Current | Proposed |
274-
| ------- | ------- | -------- |
275-
| Passive resolution | `auth({ resolvers: [env, file, ...interactive] })` | `auth({ strategies: [env, file, ...interactive] })` |
276-
| Interactive login | `ctx.auth.login()` walks global resolvers | `ctx.auth.login()` (same), or override with `{ strategies }` |
277-
| Auth enforcement | User writes custom middleware | `auth.require()` built-in |
278-
| HTTP + auth wiring | `auth({ http: { ... } })` bundled, implicit | `http({ headers: auth.headers() })` explicit |
279-
| Targeted strategy | Not possible | `ctx.auth.login({ strategies: [auth.token()] })` |
280-
| Bundle middleware | Not possible | `compose(mw1, mw2, mw3)` |
281-
| Context type safety | Global module augmentation | `middleware<{ Variables: { ... } }>()` per-middleware |
269+
| Concern | Current | Proposed |
270+
| ------------------- | -------------------------------------------------- | ------------------------------------------------------------ |
271+
| Passive resolution | `auth({ resolvers: [env, file, ...interactive] })` | `auth({ strategies: [env, file, ...interactive] })` |
272+
| Interactive login | `ctx.auth.login()` walks global resolvers | `ctx.auth.login()` (same), or override with `{ strategies }` |
273+
| Auth enforcement | User writes custom middleware | `auth.require()` built-in |
274+
| HTTP + auth wiring | `auth({ http: { ... } })` bundled, implicit | `http({ headers: auth.headers() })` explicit |
275+
| Targeted strategy | Not possible | `ctx.auth.login({ strategies: [auth.token()] })` |
276+
| Bundle middleware | Not possible | `compose(mw1, mw2, mw3)` |
277+
| Context type safety | Global module augmentation | `middleware<{ Variables: { ... } }>()` per-middleware |
282278

283279
---
284280

285281
## Build Readiness
286282

287-
| Component | Status | Notes |
288-
| --------- | ------ | ----- |
289-
| `http()` with explicit `headers` | **Ready to build** | Add `auth: false` default, remove `ctx.auth` magic. Already supports `headers: (ctx) => ...`. |
290-
| `auth.headers()` | **Ready to build** | Small factory function. Uses existing `buildAuthHeaders()`. |
291-
| `auth.require()` | **Ready to build** | Already documented as a pattern. Move into the `auth` namespace. |
292-
| `compose()` | **Ready to build** | ~10 lines. `reduceRight` over middleware handlers. |
293-
| `login({ strategies })` | Design finalized | Optional override. Requires refactoring `runStrategyChain` to accept filtered list. |
294-
| `resolvers``strategies` rename | Design finalized | Non-breaking if old key is kept as alias during migration. |
295-
| Remove `auth({ http })` option | Design finalized | Deprecate, then remove. Replaced by standalone `http()` + `auth.headers()`. |
283+
| Component | Status | Notes |
284+
| ------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
285+
| `http()` with explicit `headers` | **Ready to build** | Add `auth: false` default, remove `ctx.auth` magic. Already supports `headers: (ctx) => ...`. |
286+
| `auth.headers()` | **Ready to build** | Small factory function. Uses existing `buildAuthHeaders()`. |
287+
| `auth.require()` | **Ready to build** | Already documented as a pattern. Move into the `auth` namespace. |
288+
| `compose()` | **Ready to build** | ~10 lines. `reduceRight` over middleware handlers. |
289+
| `login({ strategies })` | Design finalized | Optional override. Requires refactoring `runStrategyChain` to accept filtered list. |
290+
| `resolvers``strategies` rename | Design finalized | Non-breaking if old key is kept as alias during migration. |
291+
| Remove `auth({ http })` option | Design finalized | Deprecate, then remove. Replaced by standalone `http()` + `auth.headers()`. |
296292
| Typed middleware (`middleware<E>()`) | **Ready to build** | Pure type-level change. Uses `decorateContext` for runtime, generics + `InferVariables` for type inference. No `ctx.set()`/`ctx.var`. |
297293

298294
---
@@ -338,7 +334,9 @@ Onion model -- root wraps command, command wraps handler. Short-circuit by not c
338334
decorateContext(ctx, 'api', httpClient)
339335

340336
declare module '@kidd-cli/core' {
341-
interface Context { readonly api: HttpClient }
337+
interface Context {
338+
readonly api: HttpClient
339+
}
342340
}
343341
```
344342

@@ -377,8 +375,8 @@ jwt({ secret: '...', alg: 'HS256' }) // auto-decorates c
377375
Lifecycle hooks, not middleware chain. Auth uses separate focused plugins composed via `@fastify/auth`:
378376

379377
```ts
380-
fastify.auth([fastify.verifyJWT, fastify.verifyApiKey], { relation: 'or' }) // OR composition
381-
fastify.auth([fastify.verifyJWT, fastify.verifyAdmin], { relation: 'and' }) // AND composition
378+
fastify.auth([fastify.verifyJWT, fastify.verifyApiKey], { relation: 'or' }) // OR composition
379+
fastify.auth([fastify.verifyJWT, fastify.verifyAdmin], { relation: 'and' }) // AND composition
382380
```
383381

384382
Each plugin (`@fastify/bearer-auth`, `@fastify/basic-auth`, `@fastify/jwt`, `@fastify/oauth2`) decorates the instance with a verification function. Request decoration is each strategy's responsibility. `@fastify/jwt` supports namespaces for multiple configs (`request.accessVerify()` vs `request.refreshVerify()`). Context typing uses module augmentation (global).

docs/concepts/authentication.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,11 @@ if (error) {
278278

279279
### AuthError
280280

281-
| AuthError `type` | Description |
282-
| ------------------ | ----------------------------------------- |
283-
| `'no_credential'` | No resolver produced a credential |
284-
| `'save_failed'` | Credential resolved but failed to persist |
285-
| `'remove_failed'` | Failed to remove the credential file |
281+
| AuthError `type` | Description |
282+
| ----------------- | ----------------------------------------- |
283+
| `'no_credential'` | No resolver produced a credential |
284+
| `'save_failed'` | Credential resolved but failed to persist |
285+
| `'remove_failed'` | Failed to remove the credential file |
286286

287287
## Requiring Authentication
288288

@@ -328,10 +328,7 @@ Apply the middleware to the root `middleware` array to enforce authentication on
328328
cli({
329329
name: 'my-app',
330330
version: '1.0.0',
331-
middleware: [
332-
auth({ resolvers: [auth.env(), auth.token()] }),
333-
requireAuth,
334-
],
331+
middleware: [auth({ resolvers: [auth.env(), auth.token()] }), requireAuth],
335332
commands: `${import.meta.dirname}/commands`,
336333
})
337334
```

examples/advanced/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ Config values are loaded from a configuration file and accessible via `ctx.confi
6464

6565
## Middleware stack
6666

67-
| Middleware | Description |
68-
| ----------- | ------------------------------------------------------------ |
69-
| `http()` | Standalone HTTP client with dynamic config-driven headers |
70-
| `timing` | Measures and logs command execution time |
71-
| `telemetry` | Tracks command invocations |
67+
| Middleware | Description |
68+
| ----------- | --------------------------------------------------------- |
69+
| `http()` | Standalone HTTP client with dynamic config-driven headers |
70+
| `timing` | Measures and logs command execution time |
71+
| `telemetry` | Tracks command invocations |
7272

7373
### Standalone `http()` middleware
7474

examples/authenticated-service/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ cli({
123123

124124
### Resolvers
125125

126-
| Resolver | Description |
127-
| --------------- | --------------------------------------------------------------------------- |
128-
| `auth.oauth()` | Opens browser, runs PKCE authorization code flow with local callback server |
126+
| Resolver | Description |
127+
| -------------- | --------------------------------------------------------------------------- |
128+
| `auth.oauth()` | Opens browser, runs PKCE authorization code flow with local callback server |
129129
| `auth.token()` | Falls back to interactive terminal input |
130130

131131
### OAuth PKCE flow

examples/authenticated-service/api/server.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,7 @@ function handleAuthorizePage(
276276
res.end(html)
277277
}
278278

279-
async function handleAuthorizeGrant(
280-
req: IncomingMessage,
281-
res: ServerResponse
282-
): Promise<void> {
279+
async function handleAuthorizeGrant(req: IncomingMessage, res: ServerResponse): Promise<void> {
283280
const raw = await readBody(req)
284281
try {
285282
const body = JSON.parse(raw) as {

packages/cli/src/commands/doctor.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,7 @@ function displayResults(
188188
* @param fixResults - The fix results to check for applied fixes.
189189
* @returns The formatted result line string.
190190
*/
191-
function formatResultLine(
192-
result: CheckResult,
193-
fixResults: readonly FixResult[]
194-
): string {
191+
function formatResultLine(result: CheckResult, fixResults: readonly FixResult[]): string {
195192
const appliedFix = fixResults.find((f) => f.name === result.name && f.fixed)
196193

197194
if (appliedFix) {
@@ -221,7 +218,8 @@ function formatDisplayStatus(status: CheckStatus | 'fix'): string {
221218
.with('pass', () => pc.green('pass'))
222219
.with('warn', () => pc.yellow('warn'))
223220
.with('fix', () => pc.blue('fix '))
224-
.otherwise(() => pc.red('fail'))
221+
.with('fail', () => pc.red('fail'))
222+
.exhaustive()
225223
}
226224

227225
/**

packages/cli/src/lib/detect.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,3 @@ async function readPackageJson(filePath: string): AsyncResult<PackageJson, Gener
102102
]
103103
}
104104
}
105-

packages/cli/src/lib/write.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,3 @@ async function writeSingleFile(
9292
]
9393
}
9494
}
95-

packages/core/CHANGELOG.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@
1111
**Auth HTTP integration:** `auth({ http: { baseUrl, namespace } })` creates authenticated HTTP clients with automatic credential header injection. Supports single or multiple clients via an array.
1212

1313
**Breaking changes:**
14-
1514
- `http()` no longer auto-reads `ctx.auth.credential()`. Use `auth({ http })` for authenticated clients or pass `headers` explicitly.
1615
- `HttpOptions.defaultHeaders` renamed to `headers` and now accepts a function `(ctx) => Record<string, string>` in addition to a static record.
1716

1817
Before:
1918

2019
```ts
2120
middleware: [
22-
auth({ resolvers: [{ source: "env" }] }),
23-
http({ baseUrl: "https://api.example.com", namespace: "api" }),
24-
];
21+
auth({ resolvers: [{ source: 'env' }] }),
22+
http({ baseUrl: 'https://api.example.com', namespace: 'api' }),
23+
]
2524
```
2625

2726
After:
@@ -30,9 +29,9 @@
3029
middleware: [
3130
auth({
3231
resolvers: [auth.env()],
33-
http: { baseUrl: "https://api.example.com", namespace: "api" },
32+
http: { baseUrl: 'https://api.example.com', namespace: 'api' },
3433
}),
35-
];
34+
]
3635
```
3736

3837
- f48ad38: Replace non-standard OAuth flow with spec-compliant PKCE (RFC 7636) and add Device Authorization Grant (RFC 8628)

0 commit comments

Comments
 (0)