@3fn/core 14.0.0 → 14.1.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 (96) hide show
  1. package/.kiro/steering/Civitas-System-Overview.md +3 -12
  2. package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
  3. package/.kiro/steering/Task-Completion-Protocol.md +7 -7
  4. package/.kiro/steering/start-up-tasks.md +4 -4
  5. package/dist/ComponentTokens.android.kt +12 -12
  6. package/dist/ComponentTokens.ios.swift +12 -12
  7. package/dist/ComponentTokens.web.css +3 -3
  8. package/dist/DesignTokens.android.kt +1 -1
  9. package/dist/DesignTokens.dtcg.json +2 -2
  10. package/dist/DesignTokens.ios.swift +1 -1
  11. package/dist/DesignTokens.web.css +1 -1
  12. package/dist/android/DesignTokens.android.kt +1 -1
  13. package/dist/browser/designerpunk.esm.js +14 -9
  14. package/dist/browser/designerpunk.esm.min.js +7 -7
  15. package/dist/browser/designerpunk.umd.js +14 -9
  16. package/dist/browser/designerpunk.umd.min.js +12 -12
  17. package/dist/browser/tokens.css +3 -3
  18. package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
  19. package/dist/build/tokens/defineComponentTokens.js +26 -0
  20. package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
  21. package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
  22. package/dist/components/core/Avatar-Base/index.d.ts +1 -1
  23. package/dist/components/core/Avatar-Base/index.js +2 -2
  24. package/dist/components/core/Button-Icon/buttonIcon.tokens.d.ts +28 -14
  25. package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
  26. package/dist/generators/TokenFileGenerator.js +7 -2
  27. package/dist/ios/DesignTokens.ios.swift +1 -1
  28. package/dist/tokens/component/progress.d.ts +65 -5
  29. package/dist/tokens/component/progress.js +79 -18
  30. package/dist/types/generated/TokenTypes.d.ts +1 -1
  31. package/dist/types/generated/TokenTypes.js +1 -1
  32. package/dist/web/DesignTokens.web.css +1 -1
  33. package/governance/BUILD-SYSTEM-SETUP.md +1 -2
  34. package/governance/Component-Development-Guide.md +1 -1
  35. package/governance/Component-Development-Standards.md +19 -18
  36. package/governance/Component-Family-Avatar.md +6 -6
  37. package/governance/Component-Family-Badge.md +19 -19
  38. package/governance/Component-Family-Button.md +26 -26
  39. package/governance/Component-Family-Chip.md +14 -14
  40. package/governance/Component-Family-Container.md +12 -12
  41. package/governance/Component-Family-Form-Inputs.md +61 -61
  42. package/governance/Component-Family-Icon.md +8 -8
  43. package/governance/Component-Family-Navigation.md +1 -1
  44. package/governance/Component-Inheritance-Structures.md +29 -28
  45. package/governance/Component-Readiness-Status.md +6 -7
  46. package/governance/Component-Templates.md +26 -26
  47. package/governance/Contract-System-Reference.md +1 -1
  48. package/governance/Process-Development-Workflow.md +4 -4
  49. package/governance/Process-File-Organization.md +1 -4
  50. package/governance/Process-Hook-Operations.md +6 -7
  51. package/governance/Process-Spec-Planning.md +8 -14
  52. package/governance/Rosetta-System-Architecture.md +7 -5
  53. package/governance/Token-Quick-Reference.md +35 -22
  54. package/governance/Web-Authoring-Standards.md +1 -1
  55. package/governance/classification-map.md +109 -3
  56. package/governance/completion-documentation-guide.md +14 -31
  57. package/governance/platform-implementation-guidelines.md +1 -2
  58. package/governance/release-management-system.md +28 -63
  59. package/package.json +2 -6
  60. package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
  61. package/src/build/tokens/defineComponentTokens.ts +43 -1
  62. package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
  63. package/src/components/core/Avatar-Base/index.ts +1 -1
  64. package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
  65. package/src/generators/TokenFileGenerator.ts +7 -2
  66. package/src/tokens/__tests__/ProgressTokenCompliance.test.ts +5 -3
  67. package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
  68. package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
  69. package/src/tokens/component/progress.ts +83 -21
  70. package/src/types/generated/TokenTypes.ts +1 -1
  71. package/token-index/components.yaml +8 -8
  72. package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
  73. package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
  74. package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
  75. package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
  76. package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
  77. package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
  78. package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
  79. package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
  80. package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
  81. package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
  82. package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
  83. package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
  84. package/src/tools/release/cli/ReleasePipeline.ts +0 -165
  85. package/src/tools/release/cli/release-tool.ts +0 -107
  86. package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
  87. package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
  88. package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
  89. package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
  90. package/src/tools/release/pipeline/TagResolver.ts +0 -40
  91. package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
  92. package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
  93. package/src/tools/release/publishers/NpmPublisher.ts +0 -196
  94. package/src/tools/release/release-config.json +0 -5
  95. package/src/tools/release/types/index.ts +0 -282
  96. package/src/tools/release/validators/SemanticVersionValidator.ts +0 -67
@@ -1,49 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Verify SemanticVersionValidator delegates correctly to VersionCalculator
4
- */
5
-
6
- import { SemanticVersionValidator } from '../validators/SemanticVersionValidator';
7
-
8
- describe('SemanticVersionValidator', () => {
9
- let validator: SemanticVersionValidator;
10
-
11
- beforeEach(() => { validator = new SemanticVersionValidator(); });
12
-
13
- describe('validateFormat', () => {
14
- it('should accept valid semver', () => {
15
- expect(validator.validateFormat('1.2.3').valid).toBe(true);
16
- expect(validator.validateFormat('0.0.1').valid).toBe(true);
17
- expect(validator.validateFormat('1.2.3-alpha.1').valid).toBe(true);
18
- expect(validator.validateFormat('1.2.3+build.123').valid).toBe(true);
19
- });
20
-
21
- it('should reject invalid semver', () => {
22
- expect(validator.validateFormat('invalid').valid).toBe(false);
23
- expect(validator.validateFormat('1.2').valid).toBe(false);
24
- expect(validator.validateFormat('').valid).toBe(false);
25
- });
26
- });
27
-
28
- describe('validateProgression', () => {
29
- it('should accept valid major bump', () => {
30
- expect(validator.validateProgression('1.2.3', '2.0.0', 'major').valid).toBe(true);
31
- });
32
-
33
- it('should accept valid minor bump', () => {
34
- expect(validator.validateProgression('1.2.3', '1.3.0', 'minor').valid).toBe(true);
35
- });
36
-
37
- it('should accept valid patch bump', () => {
38
- expect(validator.validateProgression('1.2.3', '1.2.4', 'patch').valid).toBe(true);
39
- });
40
-
41
- it('should reject invalid progression', () => {
42
- expect(validator.validateProgression('1.2.3', '1.2.5', 'patch').valid).toBe(false);
43
- });
44
-
45
- it('should reject invalid version format', () => {
46
- expect(validator.validateProgression('invalid', '1.0.0', 'major').valid).toBe(false);
47
- });
48
- });
49
- });
@@ -1,141 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Verify SummaryScanner discovers and parses summary docs correctly
4
- */
5
-
6
- import { SummaryScanner } from '../pipeline/SummaryScanner';
7
- import { TagInfo } from '../types';
8
- import { execSync } from 'child_process';
9
- import * as fs from 'fs';
10
-
11
- jest.mock('child_process');
12
- jest.mock('fs');
13
-
14
- const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
15
- const mockFs = fs as jest.Mocked<typeof fs>;
16
-
17
- describe('SummaryScanner', () => {
18
- let scanner: SummaryScanner;
19
-
20
- beforeEach(() => {
21
- jest.clearAllMocks();
22
- scanner = new SummaryScanner('/test/repo');
23
- });
24
-
25
- const tag: TagInfo = { tag: 'v7.0.0', commit: 'abc123', date: '2026-02-23' };
26
-
27
- describe('findSummariesSinceTag with tag', () => {
28
- it('should find summaries via git log', async () => {
29
- mockExecSync.mockReturnValueOnce(
30
- 'docs/specs/063-uniform-contract/task-1-summary.md\ndocs/specs/064-metadata/task-2-summary.md\n',
31
- );
32
- mockFs.readFileSync
33
- .mockReturnValueOnce('# Task 1 Summary\nContent here')
34
- .mockReturnValueOnce('# Task 2 Summary\nMore content');
35
-
36
- const results = await scanner.findSummariesSinceTag(tag);
37
-
38
- expect(results).toHaveLength(2);
39
- expect(results[0]).toEqual({
40
- path: 'docs/specs/063-uniform-contract/task-1-summary.md',
41
- specName: '063-uniform-contract',
42
- taskNumber: 1,
43
- raw: '# Task 1 Summary\nContent here',
44
- });
45
- expect(results[1].specName).toBe('064-metadata');
46
- expect(results[1].taskNumber).toBe(2);
47
-
48
- expect(mockExecSync).toHaveBeenCalledWith(
49
- expect.stringContaining('v7.0.0..HEAD'),
50
- expect.objectContaining({ cwd: '/test/repo' }),
51
- );
52
- });
53
-
54
- it('should fall back to glob when git log fails', async () => {
55
- mockExecSync
56
- .mockImplementationOnce(() => { throw new Error('git error'); })
57
- .mockReturnValueOnce('docs/specs/060-spec/task-1-summary.md\n');
58
- mockFs.readFileSync.mockReturnValueOnce('# Content');
59
-
60
- const results = await scanner.findSummariesSinceTag(tag);
61
-
62
- expect(results).toHaveLength(1);
63
- expect(results[0].specName).toBe('060-spec');
64
- });
65
-
66
- it('should return empty when no summaries found', async () => {
67
- mockExecSync.mockReturnValueOnce('\n');
68
-
69
- const results = await scanner.findSummariesSinceTag(tag);
70
-
71
- expect(results).toHaveLength(0);
72
- });
73
- });
74
-
75
- describe('findSummariesSinceTag without tag (null)', () => {
76
- it('should use glob scan when tag is null', async () => {
77
- mockExecSync.mockReturnValueOnce('docs/specs/061-tokens/task-1-summary.md\n');
78
- mockFs.readFileSync.mockReturnValueOnce('# Task 1');
79
-
80
- const results = await scanner.findSummariesSinceTag(null);
81
-
82
- expect(results).toHaveLength(1);
83
- expect(results[0].specName).toBe('061-tokens');
84
- expect(mockExecSync).toHaveBeenCalledWith(
85
- expect.stringContaining('find docs/specs'),
86
- expect.anything(),
87
- );
88
- });
89
-
90
- it('should return empty when glob finds nothing', async () => {
91
- mockExecSync.mockReturnValueOnce('');
92
-
93
- const results = await scanner.findSummariesSinceTag(null);
94
-
95
- expect(results).toHaveLength(0);
96
- });
97
- });
98
-
99
- describe('path parsing', () => {
100
- it('should extract specName and taskNumber from path', async () => {
101
- mockExecSync.mockReturnValueOnce('docs/specs/065-release-system-rebuild/task-3-summary.md\n');
102
- mockFs.readFileSync.mockReturnValueOnce('# Content');
103
-
104
- const results = await scanner.findSummariesSinceTag(null);
105
-
106
- expect(results[0].specName).toBe('065-release-system-rebuild');
107
- expect(results[0].taskNumber).toBe(3);
108
- });
109
-
110
- it('should handle double-digit task numbers', async () => {
111
- mockExecSync.mockReturnValueOnce('docs/specs/050-spec/task-12-summary.md\n');
112
- mockFs.readFileSync.mockReturnValueOnce('# Content');
113
-
114
- const results = await scanner.findSummariesSinceTag(null);
115
-
116
- expect(results[0].taskNumber).toBe(12);
117
- });
118
-
119
- it('should skip files that fail to read', async () => {
120
- mockExecSync.mockReturnValueOnce(
121
- 'docs/specs/060-spec/task-1-summary.md\ndocs/specs/061-spec/task-2-summary.md\n',
122
- );
123
- mockFs.readFileSync
124
- .mockImplementationOnce(() => { throw new Error('ENOENT'); })
125
- .mockReturnValueOnce('# Content');
126
-
127
- const results = await scanner.findSummariesSinceTag(null);
128
-
129
- expect(results).toHaveLength(1);
130
- expect(results[0].taskNumber).toBe(2);
131
- });
132
-
133
- it('should skip files with unparseable paths', async () => {
134
- mockExecSync.mockReturnValueOnce('some/random/file.md\n');
135
-
136
- const results = await scanner.findSummariesSinceTag(null);
137
-
138
- expect(results).toHaveLength(0);
139
- });
140
- });
141
- });
@@ -1,91 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Verify TagResolver resolves git tags correctly
4
- */
5
-
6
- import { TagResolver } from '../pipeline/TagResolver';
7
- import { execSync } from 'child_process';
8
-
9
- jest.mock('child_process');
10
- const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
11
-
12
- describe('TagResolver', () => {
13
- let resolver: TagResolver;
14
-
15
- beforeEach(() => {
16
- jest.clearAllMocks();
17
- resolver = new TagResolver('/test/repo');
18
- });
19
-
20
- describe('getLatestTag', () => {
21
- it('should return tag info when tags exist', async () => {
22
- mockExecSync
23
- .mockReturnValueOnce('v7.0.0\n')
24
- .mockReturnValueOnce('abc123def\n')
25
- .mockReturnValueOnce('2026-02-23T12:00:00-05:00\n');
26
-
27
- const result = await resolver.getLatestTag();
28
-
29
- expect(result).toEqual({
30
- tag: 'v7.0.0',
31
- commit: 'abc123def',
32
- date: '2026-02-23T12:00:00-05:00',
33
- });
34
- expect(mockExecSync).toHaveBeenCalledWith(
35
- 'git describe --tags --abbrev=0',
36
- expect.objectContaining({ cwd: '/test/repo' }),
37
- );
38
- });
39
-
40
- it('should return null when no tags exist', async () => {
41
- mockExecSync.mockImplementationOnce(() => { throw new Error('fatal: No names found'); });
42
-
43
- const result = await resolver.getLatestTag();
44
-
45
- expect(result).toBeNull();
46
- });
47
-
48
- it('should return null when git is not available', async () => {
49
- mockExecSync.mockImplementationOnce(() => { throw new Error('command not found: git'); });
50
-
51
- const result = await resolver.getLatestTag();
52
-
53
- expect(result).toBeNull();
54
- });
55
- });
56
-
57
- describe('createTag', () => {
58
- it('should create annotated tag with v prefix', async () => {
59
- mockExecSync.mockReturnValueOnce('');
60
-
61
- await resolver.createTag('8.0.0', 'Release v8.0.0');
62
-
63
- expect(mockExecSync).toHaveBeenCalledWith(
64
- 'git tag -a v8.0.0 -m "Release v8.0.0"',
65
- expect.objectContaining({ cwd: '/test/repo' }),
66
- );
67
- });
68
-
69
- it('should not double-prefix v', async () => {
70
- mockExecSync.mockReturnValueOnce('');
71
-
72
- await resolver.createTag('v8.0.0', 'Release v8.0.0');
73
-
74
- expect(mockExecSync).toHaveBeenCalledWith(
75
- 'git tag -a v8.0.0 -m "Release v8.0.0"',
76
- expect.objectContaining({ cwd: '/test/repo' }),
77
- );
78
- });
79
-
80
- it('should escape quotes in message', async () => {
81
- mockExecSync.mockReturnValueOnce('');
82
-
83
- await resolver.createTag('8.0.0', 'Release with "quotes"');
84
-
85
- expect(mockExecSync).toHaveBeenCalledWith(
86
- 'git tag -a v8.0.0 -m "Release with \\"quotes\\""',
87
- expect.objectContaining({ cwd: '/test/repo' }),
88
- );
89
- });
90
- });
91
- });
@@ -1,270 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Verify VersionCalculator functionality works correctly
4
- */
5
-
6
- import { VersionCalculator } from '../pipeline/VersionCalculator';
7
- import { ExtractedChanges, BreakingChange, Feature, BugFix, Improvement } from '../types';
8
-
9
- describe('VersionCalculator', () => {
10
- let calculator: VersionCalculator;
11
-
12
- beforeEach(() => {
13
- calculator = new VersionCalculator();
14
- });
15
-
16
- const baseChanges: ExtractedChanges = {
17
- breakingChanges: [],
18
- newFeatures: [],
19
- bugFixes: [],
20
- improvements: [],
21
- documentation: [],
22
- metadata: {
23
- documentsAnalyzed: 1,
24
- extractionConfidence: 0.9,
25
- ambiguousItems: [],
26
- filteredItems: [],
27
- },
28
- };
29
-
30
- describe('calculateVersionBump', () => {
31
- it('should recommend major version bump for breaking changes', () => {
32
- const changes: ExtractedChanges = {
33
- ...baseChanges,
34
- breakingChanges: [{
35
- id: '1', title: 'Remove deprecated API', description: 'Removed old authentication method',
36
- affectedAPIs: ['auth.login'], source: 'task-1-completion.md', severity: 'high',
37
- }],
38
- };
39
-
40
- const result = calculator.calculateVersionBump(changes, '1.2.3');
41
- expect(result.bumpType).toBe('major');
42
- expect(result.recommendedVersion).toBe('2.0.0');
43
- expect(result.evidence).toHaveLength(1);
44
- expect(result.evidence[0].type).toBe('breaking');
45
- expect(result.rationale).toContain('Major version bump required');
46
- });
47
-
48
- it('should recommend minor version bump for new features', () => {
49
- const changes: ExtractedChanges = {
50
- ...baseChanges,
51
- newFeatures: [{
52
- id: '1', title: 'Add user preferences', description: 'Users can now customize their dashboard',
53
- benefits: ['Better UX'], requirements: ['REQ-1'], artifacts: ['UserPreferences.ts'],
54
- source: 'task-2-completion.md', category: 'enhancement',
55
- }],
56
- };
57
-
58
- const result = calculator.calculateVersionBump(changes, '1.2.3');
59
- expect(result.bumpType).toBe('minor');
60
- expect(result.recommendedVersion).toBe('1.3.0');
61
- expect(result.evidence).toHaveLength(1);
62
- expect(result.evidence[0].type).toBe('feature');
63
- expect(result.rationale).toContain('Minor version bump recommended');
64
- });
65
-
66
- it('should recommend patch version bump for bug fixes', () => {
67
- const changes: ExtractedChanges = {
68
- ...baseChanges,
69
- bugFixes: [{
70
- id: '1', title: 'Fix login validation', description: 'Fixed email validation regex',
71
- affectedComponents: ['LoginForm'], source: 'task-3-completion.md', severity: 'medium',
72
- }],
73
- };
74
-
75
- const result = calculator.calculateVersionBump(changes, '1.2.3');
76
- expect(result.bumpType).toBe('patch');
77
- expect(result.recommendedVersion).toBe('1.2.4');
78
- expect(result.evidence).toHaveLength(1);
79
- expect(result.evidence[0].type).toBe('fix');
80
- expect(result.rationale).toContain('Patch version bump recommended');
81
- });
82
-
83
- it('should recommend patch version bump for improvements', () => {
84
- const changes: ExtractedChanges = {
85
- ...baseChanges,
86
- improvements: [{
87
- id: '1', title: 'Optimize database queries', description: 'Improved query performance by 50%',
88
- type: 'performance', impact: 'high', source: 'task-4-completion.md',
89
- }],
90
- };
91
-
92
- const result = calculator.calculateVersionBump(changes, '1.2.3');
93
- expect(result.bumpType).toBe('patch');
94
- expect(result.recommendedVersion).toBe('1.2.4');
95
- expect(result.evidence).toHaveLength(1);
96
- expect(result.evidence[0].type).toBe('improvement');
97
- });
98
-
99
- it('should recommend no version bump for documentation only', () => {
100
- const changes: ExtractedChanges = {
101
- ...baseChanges,
102
- documentation: [{
103
- id: '1', title: 'Update README', description: 'Added installation instructions',
104
- type: 'readme', source: 'task-5-completion.md',
105
- }],
106
- };
107
-
108
- const result = calculator.calculateVersionBump(changes, '1.2.3');
109
- expect(result.bumpType).toBe('none');
110
- expect(result.recommendedVersion).toBe('1.2.3');
111
- expect(result.rationale).toContain('No version bump required');
112
- });
113
-
114
- it('should prioritize breaking changes over features', () => {
115
- const changes: ExtractedChanges = {
116
- ...baseChanges,
117
- breakingChanges: [{
118
- id: '1', title: 'Remove deprecated API', description: 'Removed old method',
119
- affectedAPIs: ['old.method'], source: 'task-1-completion.md', severity: 'high',
120
- }],
121
- newFeatures: [{
122
- id: '2', title: 'Add new feature', description: 'Added cool feature',
123
- benefits: ['Better UX'], requirements: ['REQ-1'], artifacts: ['Feature.ts'],
124
- source: 'task-2-completion.md', category: 'enhancement',
125
- }],
126
- };
127
-
128
- const result = calculator.calculateVersionBump(changes, '1.2.3');
129
- expect(result.bumpType).toBe('major');
130
- expect(result.recommendedVersion).toBe('2.0.0');
131
- expect(result.evidence).toHaveLength(2);
132
- });
133
-
134
- it('should calculate confidence based on extraction metadata', () => {
135
- const changes: ExtractedChanges = {
136
- ...baseChanges,
137
- newFeatures: [{
138
- id: '1', title: 'Add feature', description: 'New feature',
139
- benefits: [], requirements: [], artifacts: [], source: 'test.md', category: 'enhancement',
140
- }],
141
- metadata: {
142
- documentsAnalyzed: 1, extractionConfidence: 0.8,
143
- ambiguousItems: ['unclear item', 'another unclear item'], filteredItems: [],
144
- },
145
- };
146
-
147
- const result = calculator.calculateVersionBump(changes, '1.2.3');
148
- expect(result.confidence).toBeLessThan(0.85);
149
- expect(result.confidence).toBeGreaterThan(0.75);
150
- });
151
- });
152
-
153
- describe('validateSemanticVersioning', () => {
154
- it('should validate correct version progression', () => {
155
- const result = calculator.validateSemanticVersioning({
156
- currentVersion: '1.2.3', recommendedVersion: '2.0.0', bumpType: 'major',
157
- rationale: 'Breaking changes', confidence: 0.9,
158
- evidence: [{ type: 'breaking', description: 'Breaking change', source: 'test.md', impact: 'high' }],
159
- });
160
- expect(result.valid).toBe(true);
161
- expect(result.errors).toHaveLength(0);
162
- });
163
-
164
- it('should detect invalid version format', () => {
165
- const result = calculator.validateSemanticVersioning({
166
- currentVersion: 'invalid', recommendedVersion: '2.0.0', bumpType: 'major',
167
- rationale: 'Breaking changes', confidence: 0.9, evidence: [],
168
- });
169
- expect(result.valid).toBe(false);
170
- expect(result.errors).toContain('Current version "invalid" is not a valid semantic version');
171
- });
172
-
173
- it('should detect invalid version progression', () => {
174
- const result = calculator.validateSemanticVersioning({
175
- currentVersion: '1.2.3', recommendedVersion: '1.2.5', bumpType: 'patch',
176
- rationale: 'Bug fixes', confidence: 0.9, evidence: [],
177
- });
178
- expect(result.valid).toBe(false);
179
- expect(result.errors).toContain('Version progression from 1.2.3 to 1.2.5 is invalid for patch bump');
180
- });
181
-
182
- it('should warn about low confidence', () => {
183
- const result = calculator.validateSemanticVersioning({
184
- currentVersion: '1.2.3', recommendedVersion: '1.3.0', bumpType: 'minor',
185
- rationale: 'New features', confidence: 0.6, evidence: [],
186
- });
187
- expect(result.valid).toBe(true);
188
- expect(result.warnings).toContain('Low confidence score (0.60) - manual review recommended');
189
- });
190
- });
191
-
192
- describe('generateVersionRationale', () => {
193
- it('should generate rationale for breaking changes', () => {
194
- const changes: ExtractedChanges = {
195
- ...baseChanges,
196
- breakingChanges: [
197
- { id: '1', title: 'Remove old API', description: 'Removed deprecated method', affectedAPIs: ['old.method'], source: 'test.md', severity: 'high' },
198
- { id: '2', title: 'Change interface', description: 'Modified interface signature', affectedAPIs: ['interface.method'], source: 'test.md', severity: 'medium' },
199
- ],
200
- };
201
-
202
- const rationale = calculator.generateVersionRationale(changes);
203
- expect(rationale).toContain('Major version bump required due to 2 breaking change(s)');
204
- expect(rationale).toContain('Remove old API (high severity)');
205
- expect(rationale).toContain('Change interface (medium severity)');
206
- });
207
-
208
- it('should limit rationale items for readability', () => {
209
- const changes: ExtractedChanges = {
210
- ...baseChanges,
211
- breakingChanges: Array.from({ length: 5 }, (_, i) => ({
212
- id: `${i + 1}`, title: `Breaking change ${i + 1}`, description: `Description ${i + 1}`,
213
- affectedAPIs: [`api${i + 1}`], source: 'test.md', severity: 'medium' as const,
214
- })),
215
- };
216
-
217
- const rationale = calculator.generateVersionRationale(changes);
218
- expect(rationale).toContain('Major version bump required due to 5 breaking change(s)');
219
- expect(rationale).toContain('... and 2 more breaking changes');
220
- });
221
- });
222
-
223
- describe('handlePreReleaseVersions', () => {
224
- it('should increment pre-release version', () => {
225
- expect(calculator.handlePreReleaseVersions('1.2.3-alpha.1', 'patch')).toBe('1.2.3-alpha.2');
226
- });
227
-
228
- it('should handle pre-release without number', () => {
229
- expect(calculator.handlePreReleaseVersions('1.2.3-beta', 'patch')).toBe('1.2.3-beta.1');
230
- });
231
-
232
- it('should handle rc pre-release', () => {
233
- expect(calculator.handlePreReleaseVersions('1.2.3-rc.2', 'patch')).toBe('1.2.3-rc.3');
234
- });
235
-
236
- it('should calculate normal version for non-pre-release', () => {
237
- expect(calculator.handlePreReleaseVersions('1.2.3', 'minor')).toBe('1.3.0');
238
- });
239
- });
240
-
241
- describe('edge cases', () => {
242
- it('should handle empty changes', () => {
243
- const changes: ExtractedChanges = {
244
- ...baseChanges,
245
- metadata: { documentsAnalyzed: 0, extractionConfidence: 1, ambiguousItems: [], filteredItems: [] },
246
- };
247
- const result = calculator.calculateVersionBump(changes, '1.2.3');
248
- expect(result.bumpType).toBe('none');
249
- expect(result.recommendedVersion).toBe('1.2.3');
250
- });
251
-
252
- it('should handle invalid version format gracefully', () => {
253
- expect(() => calculator.calculateVersionBump({} as ExtractedChanges, 'invalid-version'))
254
- .toThrow('Invalid semantic version: invalid-version');
255
- });
256
-
257
- it('should handle version with build metadata', () => {
258
- const changes: ExtractedChanges = {
259
- ...baseChanges,
260
- newFeatures: [{
261
- id: '1', title: 'New feature', description: 'Added feature',
262
- benefits: [], requirements: [], artifacts: [], source: 'test.md', category: 'enhancement',
263
- }],
264
- };
265
- const result = calculator.calculateVersionBump(changes, '1.2.3+build.123');
266
- expect(result.bumpType).toBe('minor');
267
- expect(result.recommendedVersion).toBe('1.3.0');
268
- });
269
- });
270
- });
@@ -1,80 +0,0 @@
1
- /**
2
- * NpmMockHelper - Utility for mocking npm operations in tests
3
- *
4
- * Extracted from src/release/publishing/__tests__/helpers/NpmMockHelper.ts
5
- */
6
-
7
- import { execSync } from 'child_process';
8
-
9
- export class NpmMockHelper {
10
- private mockExecSync: jest.MockedFunction<typeof execSync>;
11
-
12
- constructor(mockExecSync: jest.MockedFunction<typeof execSync>) {
13
- this.mockExecSync = mockExecSync;
14
- }
15
-
16
- mockAuthentication(authenticated: boolean, username: string = 'testuser', email: string = 'test@example.com'): void {
17
- if (authenticated) {
18
- this.mockExecSync.mockReturnValueOnce(`${username}\n`);
19
- this.mockExecSync.mockReturnValueOnce(JSON.stringify({ email }) as any);
20
- } else {
21
- this.mockExecSync.mockImplementationOnce(() => { throw new Error('Not logged in'); });
22
- }
23
- }
24
-
25
- mockPublishSuccess(packageName: string, version: string = '1.0.0', username: string = 'testuser', email: string = 'test@example.com'): void {
26
- this.mockExecSync.mockReturnValueOnce(`${username}\n`);
27
- this.mockExecSync.mockReturnValueOnce(JSON.stringify({ email }) as any);
28
- this.mockExecSync.mockReturnValueOnce('');
29
- this.mockExecSync.mockReturnValueOnce(`+ ${packageName}@${version}\n` as any);
30
- }
31
-
32
- mockPackageExists(packageName: string, version: string = '1.0.0', username: string = 'testuser', email: string = 'test@example.com'): void {
33
- this.mockExecSync.mockReturnValueOnce(`${username}\n`);
34
- this.mockExecSync.mockReturnValueOnce(JSON.stringify({ email }) as any);
35
- this.mockExecSync.mockReturnValueOnce(`"${version}"\n` as any);
36
- }
37
-
38
- mockUnpublish(packageName: string, version: string = '1.0.0'): void {
39
- this.mockExecSync.mockReturnValueOnce(`"${version}"\n` as any);
40
- this.mockExecSync.mockReturnValueOnce('');
41
- }
42
-
43
- mockAuthenticationFailure(): void {
44
- this.mockExecSync.mockImplementationOnce(() => { throw new Error('Not logged in'); });
45
- }
46
-
47
- mockPublishFailure(errorMessage: string = 'Publish failed', packageName: string = '@test/package', version: string = '1.0.0', username: string = 'testuser', email: string = 'test@example.com'): void {
48
- this.mockExecSync.mockReturnValueOnce(`${username}\n`);
49
- this.mockExecSync.mockReturnValueOnce(JSON.stringify({ email }) as any);
50
- this.mockExecSync.mockReturnValueOnce('');
51
- this.mockExecSync.mockImplementationOnce(() => { throw new Error(errorMessage); });
52
- }
53
-
54
- mockPublishWithRetry(packageName: string, version: string = '1.0.0', failureCount: number = 2, username: string = 'testuser', email: string = 'test@example.com'): void {
55
- for (let i = 0; i < failureCount; i++) {
56
- this.mockExecSync.mockReturnValueOnce(`${username}\n`);
57
- this.mockExecSync.mockReturnValueOnce(JSON.stringify({ email }) as any);
58
- this.mockExecSync.mockReturnValueOnce('');
59
- this.mockExecSync.mockImplementationOnce(() => { throw new Error('Temporary network error'); });
60
- }
61
- this.mockExecSync.mockReturnValueOnce(`${username}\n`);
62
- this.mockExecSync.mockReturnValueOnce(JSON.stringify({ email }) as any);
63
- this.mockExecSync.mockReturnValueOnce('');
64
- this.mockExecSync.mockReturnValueOnce(`+ ${packageName}@${version}\n` as any);
65
- }
66
-
67
- mockUnpublishFailure(errorMessage: string = 'Unpublish failed', packageName: string = '@test/package', version: string = '1.0.0'): void {
68
- this.mockExecSync.mockReturnValueOnce(`"${version}"\n` as any);
69
- this.mockExecSync.mockImplementationOnce(() => { throw new Error(errorMessage); });
70
- }
71
-
72
- cleanup(): void {
73
- this.mockExecSync.mockClear();
74
- this.mockExecSync.mockReset();
75
- }
76
-
77
- getMock(): jest.MockedFunction<typeof execSync> {
78
- return this.mockExecSync;
79
- }
80
- }