Skip to content

Commit 9880d83

Browse files
committed
Release v1.0.2 — per-step retry override support
Add retryOverride to sequence steps: TOML parsing, validation, rendering, diff display with all retry fields, and normalization to prevent phantom diffs.
1 parent 9db82d8 commit 9880d83

8 files changed

Lines changed: 655 additions & 8 deletions

File tree

RELEASING.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Releasing posthook CLI
2+
3+
## Release repo
4+
5+
https://github.com/posthook/cli
6+
7+
## Registry
8+
9+
https://www.npmjs.com/package/posthook
10+
11+
## Steps
12+
13+
1. **Bump the version** (updates `package.json` and creates a git tag):
14+
15+
```bash
16+
npm version patch # or minor / major
17+
```
18+
19+
2. **Run tests**:
20+
21+
```bash
22+
npm test
23+
```
24+
25+
3. **Publish to npm**:
26+
27+
```bash
28+
npm publish
29+
```
30+
31+
4. **Push the commit and tag**:
32+
33+
```bash
34+
git push origin main --tags
35+
```
36+
37+
5. **Create GitHub release** (optional):
38+
39+
```bash
40+
gh release create vX.Y.Z --title "vX.Y.Z" --notes "Release notes here"
41+
```
42+
43+
## Prerequisites
44+
45+
- npm account with publish access to the `posthook` package
46+
- Logged in via `npm login`
47+
48+
## Versioning
49+
50+
Follow [semver](https://semver.org/):
51+
52+
- **Patch** (1.0.x): Bug fixes, doc updates
53+
- **Minor** (1.x.0): New features, backward-compatible changes
54+
- **Major** (x.0.0): Breaking API changes

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "posthook",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "Posthook CLI - Manage projects and receive webhook deliveries locally",
55
"type": "module",
66
"bin": {

src/lib/__tests__/diff.test.ts

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,246 @@ describe('diff', () => {
333333
expect(diff.modified[0].changes).toContainEqual(expect.stringContaining('step "s1" data'));
334334
});
335335

336+
it('detects retry_override added to step', () => {
337+
const withRetry: SequenceToml = {
338+
...intervalSequence,
339+
steps: {
340+
poll: {
341+
path: '/webhooks/poll',
342+
retry_override: {
343+
min_retries: 3,
344+
delay_seconds: 10,
345+
strategy: 'fixed',
346+
jitter: false,
347+
},
348+
},
349+
},
350+
};
351+
const diff = computeSequenceDiff([withRetry], [intervalSequence]);
352+
expect(diff.modified).toHaveLength(1);
353+
expect(diff.modified[0].changes).toContainEqual(
354+
expect.stringContaining('step "poll" retry_override')
355+
);
356+
});
357+
358+
it('detects retry_override removed from step', () => {
359+
const withRetry: SequenceToml = {
360+
...intervalSequence,
361+
steps: {
362+
poll: {
363+
path: '/webhooks/poll',
364+
retry_override: {
365+
min_retries: 3,
366+
delay_seconds: 10,
367+
strategy: 'fixed',
368+
jitter: false,
369+
},
370+
},
371+
},
372+
};
373+
const diff = computeSequenceDiff([intervalSequence], [withRetry]);
374+
expect(diff.modified).toHaveLength(1);
375+
expect(diff.modified[0].changes).toContainEqual(
376+
expect.stringContaining('step "poll" retry_override: fixed, 3 retries, 10s delay → (project defaults)')
377+
);
378+
});
379+
380+
it('detects retry_override strategy change', () => {
381+
const fixed: SequenceToml = {
382+
...intervalSequence,
383+
steps: {
384+
poll: {
385+
path: '/webhooks/poll',
386+
retry_override: {
387+
min_retries: 3,
388+
delay_seconds: 10,
389+
strategy: 'fixed',
390+
jitter: false,
391+
},
392+
},
393+
},
394+
};
395+
const exponential: SequenceToml = {
396+
...intervalSequence,
397+
steps: {
398+
poll: {
399+
path: '/webhooks/poll',
400+
retry_override: {
401+
min_retries: 3,
402+
delay_seconds: 10,
403+
strategy: 'exponential',
404+
backoff_factor: 2.0,
405+
max_delay_seconds: 300,
406+
jitter: true,
407+
},
408+
},
409+
},
410+
};
411+
const diff = computeSequenceDiff([exponential], [fixed]);
412+
expect(diff.modified).toHaveLength(1);
413+
expect(diff.modified[0].changes).toContainEqual(
414+
expect.stringContaining('step "poll" retry_override: fixed, 3 retries, 10s delay → exponential, 3 retries, 10s delay, 2x backoff, 300s max, jitter')
415+
);
416+
});
417+
418+
it('no diff when retry_override is identical', () => {
419+
const withRetry: SequenceToml = {
420+
...intervalSequence,
421+
steps: {
422+
poll: {
423+
path: '/webhooks/poll',
424+
retry_override: {
425+
min_retries: 3,
426+
delay_seconds: 10,
427+
strategy: 'fixed',
428+
jitter: false,
429+
},
430+
},
431+
},
432+
};
433+
const diff = computeSequenceDiff([withRetry], [{ ...withRetry }]);
434+
expect(diff.modified).toHaveLength(0);
435+
});
436+
437+
it('no diff when exponential backoff_factor defaults to 2.0', () => {
438+
const localWithoutBackoff: SequenceToml = {
439+
...intervalSequence,
440+
steps: {
441+
poll: {
442+
path: '/webhooks/poll',
443+
retry_override: {
444+
min_retries: 3,
445+
delay_seconds: 10,
446+
strategy: 'exponential',
447+
max_delay_seconds: 300,
448+
jitter: false,
449+
},
450+
},
451+
},
452+
};
453+
const remoteWithBackoff: SequenceToml = {
454+
...intervalSequence,
455+
steps: {
456+
poll: {
457+
path: '/webhooks/poll',
458+
retry_override: {
459+
min_retries: 3,
460+
delay_seconds: 10,
461+
strategy: 'exponential',
462+
backoff_factor: 2.0,
463+
max_delay_seconds: 300,
464+
jitter: false,
465+
},
466+
},
467+
},
468+
};
469+
const diff = computeSequenceDiff([localWithoutBackoff], [remoteWithBackoff]);
470+
expect(diff.modified).toHaveLength(0);
471+
});
472+
473+
it('no diff when jitter defaults to false', () => {
474+
const localNoJitter: SequenceToml = {
475+
...intervalSequence,
476+
steps: {
477+
poll: {
478+
path: '/webhooks/poll',
479+
retry_override: {
480+
min_retries: 3,
481+
delay_seconds: 10,
482+
strategy: 'fixed',
483+
} as SequenceStepToml['retry_override'] & object,
484+
},
485+
},
486+
};
487+
const remoteWithJitter: SequenceToml = {
488+
...intervalSequence,
489+
steps: {
490+
poll: {
491+
path: '/webhooks/poll',
492+
retry_override: {
493+
min_retries: 3,
494+
delay_seconds: 10,
495+
strategy: 'fixed',
496+
jitter: false,
497+
},
498+
},
499+
},
500+
};
501+
const diff = computeSequenceDiff([localNoJitter], [remoteWithJitter]);
502+
expect(diff.modified).toHaveLength(0);
503+
});
504+
505+
it('no diff when fixed strategy strips backoff fields', () => {
506+
const localWithExtra: SequenceToml = {
507+
...intervalSequence,
508+
steps: {
509+
poll: {
510+
path: '/webhooks/poll',
511+
retry_override: {
512+
min_retries: 3,
513+
delay_seconds: 10,
514+
strategy: 'fixed',
515+
backoff_factor: 2.0,
516+
max_delay_seconds: 300,
517+
jitter: false,
518+
},
519+
},
520+
},
521+
};
522+
const remoteClean: SequenceToml = {
523+
...intervalSequence,
524+
steps: {
525+
poll: {
526+
path: '/webhooks/poll',
527+
retry_override: {
528+
min_retries: 3,
529+
delay_seconds: 10,
530+
strategy: 'fixed',
531+
jitter: false,
532+
},
533+
},
534+
},
535+
};
536+
const diff = computeSequenceDiff([localWithExtra], [remoteClean]);
537+
expect(diff.modified).toHaveLength(0);
538+
});
539+
540+
it('still detects actual retry_override changes after normalization', () => {
541+
const local: SequenceToml = {
542+
...intervalSequence,
543+
steps: {
544+
poll: {
545+
path: '/webhooks/poll',
546+
retry_override: {
547+
min_retries: 5,
548+
delay_seconds: 10,
549+
strategy: 'fixed',
550+
jitter: false,
551+
},
552+
},
553+
},
554+
};
555+
const remote: SequenceToml = {
556+
...intervalSequence,
557+
steps: {
558+
poll: {
559+
path: '/webhooks/poll',
560+
retry_override: {
561+
min_retries: 3,
562+
delay_seconds: 10,
563+
strategy: 'fixed',
564+
jitter: false,
565+
},
566+
},
567+
},
568+
};
569+
const diff = computeSequenceDiff([local], [remote]);
570+
expect(diff.modified).toHaveLength(1);
571+
expect(diff.modified[0].changes).toContainEqual(
572+
expect.stringContaining('step "poll" retry_override')
573+
);
574+
});
575+
336576
it('handles empty local and remote sequences', () => {
337577
const diff = computeSequenceDiff([], []);
338578
expect(diff.added).toEqual([]);

0 commit comments

Comments
 (0)