@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,289 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Verify NPM publisher handles package publishing correctly
4
- */
5
-
6
- import { NpmPublisher, NpmConfig } from '../publishers/NpmPublisher';
7
- import { PackagePublish } from '../types';
8
- import { execSync } from 'child_process';
9
- import * as fs from 'fs';
10
- import { NpmMockHelper } from './helpers/NpmMockHelper';
11
-
12
- jest.mock('child_process');
13
- const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
14
-
15
- jest.mock('fs');
16
- const mockFs = fs as jest.Mocked<typeof fs>;
17
-
18
- describe('NpmPublisher', () => {
19
- let publisher: NpmPublisher;
20
- let config: NpmConfig;
21
- let npmHelper: NpmMockHelper;
22
-
23
- beforeEach(() => {
24
- jest.clearAllMocks();
25
- config = { registry: 'https://registry.npmjs.org/', timeout: 60000, maxRetries: 3, retryDelay: 100, dryRun: false };
26
- publisher = new NpmPublisher(config);
27
- npmHelper = new NpmMockHelper(mockExecSync);
28
- });
29
-
30
- afterEach(() => { mockExecSync.mockReset(); });
31
-
32
- describe('validateAuthentication', () => {
33
- it('should return authenticated status when user is logged in', async () => {
34
- mockExecSync.mockReturnValueOnce('testuser\n' as any);
35
- mockExecSync.mockReturnValueOnce(JSON.stringify({ email: 'test@example.com' }) as any);
36
- const result = await publisher.validateAuthentication();
37
- expect(result.authenticated).toBe(true);
38
- expect(result.username).toBe('testuser');
39
- expect(result.email).toBe('test@example.com');
40
- expect(result.registry).toBe('https://registry.npmjs.org/');
41
- });
42
-
43
- it('should return unauthenticated status when user is not logged in', async () => {
44
- mockExecSync.mockImplementationOnce(() => { throw new Error('Not logged in'); });
45
- const result = await publisher.validateAuthentication();
46
- expect(result.authenticated).toBe(false);
47
- expect(result.username).toBeUndefined();
48
- });
49
-
50
- it('should handle profile parsing errors gracefully', async () => {
51
- mockExecSync.mockReturnValueOnce('testuser\n' as any);
52
- mockExecSync.mockReturnValueOnce('invalid json' as any);
53
- const result = await publisher.validateAuthentication();
54
- expect(result.authenticated).toBe(true);
55
- expect(result.username).toBe('testuser');
56
- expect(result.email).toBeUndefined();
57
- });
58
- });
59
-
60
- describe('packageVersionExists', () => {
61
- it('should return true when package version exists', async () => {
62
- mockExecSync.mockReturnValueOnce('"1.0.0"\n' as any);
63
- expect(await publisher.packageVersionExists('@test/package', '1.0.0')).toBe(true);
64
- });
65
-
66
- it('should return false when package version does not exist', async () => {
67
- mockExecSync.mockImplementationOnce(() => { throw new Error('Package not found'); });
68
- expect(await publisher.packageVersionExists('@test/package', '1.0.0')).toBe(false);
69
- });
70
- });
71
-
72
- describe('getPackageInfo', () => {
73
- it('should return package info for existing package', async () => {
74
- mockExecSync.mockReturnValueOnce(JSON.stringify(['1.0.0', '1.1.0', '2.0.0']) as any);
75
- const info = await publisher.getPackageInfo('@test/package');
76
- expect(info.name).toBe('@test/package');
77
- expect(info.exists).toBe(true);
78
- expect(info.version).toBe('2.0.0');
79
- expect(info.publishedVersions).toEqual(['1.0.0', '1.1.0', '2.0.0']);
80
- });
81
-
82
- it('should return default info for non-existent package', async () => {
83
- mockExecSync.mockImplementationOnce(() => { throw new Error('Package not found'); });
84
- const info = await publisher.getPackageInfo('@test/package');
85
- expect(info.exists).toBe(false);
86
- expect(info.version).toBe('0.0.0');
87
- });
88
-
89
- it('should handle single version response', async () => {
90
- mockExecSync.mockReturnValueOnce('"1.0.0"' as any);
91
- const info = await publisher.getPackageInfo('@test/package');
92
- expect(info.publishedVersions).toEqual(['1.0.0']);
93
- });
94
- });
95
-
96
- describe('validatePackage', () => {
97
- const testPackage: PackagePublish = { name: '@test/package', version: '1.0.0', path: '/test/path', registry: 'https://registry.npmjs.org/', access: 'public' };
98
-
99
- it('should validate package successfully', async () => {
100
- mockFs.existsSync.mockReturnValue(true);
101
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '1.0.0' }));
102
- const result = await publisher.validatePackage(testPackage);
103
- expect(result.valid).toBe(true);
104
- });
105
-
106
- it('should fail validation when package.json is missing', async () => {
107
- mockFs.existsSync.mockReturnValue(false);
108
- const result = await publisher.validatePackage(testPackage);
109
- expect(result.valid).toBe(false);
110
- expect(result.errors).toContain('package.json not found at /test/path/package.json');
111
- });
112
-
113
- it('should fail validation when package name mismatches', async () => {
114
- mockFs.existsSync.mockReturnValue(true);
115
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@different/package', version: '1.0.0' }));
116
- const result = await publisher.validatePackage(testPackage);
117
- expect(result.valid).toBe(false);
118
- expect(result.errors).toContain('Package name mismatch: expected @test/package, got @different/package');
119
- });
120
-
121
- it('should fail validation when version mismatches', async () => {
122
- mockFs.existsSync.mockReturnValue(true);
123
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '2.0.0' }));
124
- const result = await publisher.validatePackage(testPackage);
125
- expect(result.valid).toBe(false);
126
- expect(result.errors).toContain('Version mismatch: expected 1.0.0, got 2.0.0');
127
- });
128
-
129
- it('should fail validation for invalid semver', async () => {
130
- mockFs.existsSync.mockReturnValue(true);
131
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: 'invalid-version' }));
132
- const result = await publisher.validatePackage({ ...testPackage, version: 'invalid-version' });
133
- expect(result.valid).toBe(false);
134
- expect(result.errors).toContain('Invalid semver version: invalid-version');
135
- });
136
- });
137
-
138
- describe('publishPackage', () => {
139
- const testPackage: PackagePublish = { name: '@test/package', version: '1.0.0', path: '/test/path', registry: 'https://registry.npmjs.org/', access: 'public' };
140
-
141
- beforeEach(() => {
142
- mockFs.existsSync.mockReturnValue(true);
143
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '1.0.0' }));
144
- });
145
-
146
- it('should publish package successfully', async () => {
147
- npmHelper.mockPublishSuccess('@test/package', '1.0.0');
148
- const result = await publisher.publishPackage(testPackage);
149
- expect(result.success).toBe(true);
150
- expect(result.packageName).toBe('@test/package');
151
- expect(result.version).toBe('1.0.0');
152
- });
153
-
154
- it('should fail when package version already exists', async () => {
155
- npmHelper.mockPackageExists('@test/package', '1.0.0');
156
- const result = await publisher.publishPackage(testPackage);
157
- expect(result.success).toBe(false);
158
- expect(result.error).toContain('already exists in registry');
159
- });
160
-
161
- it('should fail when not authenticated', async () => {
162
- jest.clearAllMocks();
163
- const unauthPublisher = new NpmPublisher(config);
164
- const unauthHelper = new NpmMockHelper(mockExecSync);
165
- unauthHelper.mockAuthenticationFailure();
166
- mockFs.existsSync.mockReturnValue(true);
167
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '1.0.0' }));
168
- const result = await unauthPublisher.publishPackage(testPackage);
169
- expect(result.success).toBe(false);
170
- expect(result.error).toContain('Not authenticated');
171
- });
172
-
173
- it('should retry on publish failure', async () => {
174
- jest.clearAllMocks();
175
- const testHelper = new NpmMockHelper(mockExecSync);
176
- mockFs.existsSync.mockReturnValue(true);
177
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '1.0.0' }));
178
- testHelper.mockPublishWithRetry('@test/package', '1.0.0', 1);
179
- const result = await publisher.publishPackage(testPackage);
180
- expect(result.success).toBe(true);
181
- });
182
-
183
- it('should include access flag in publish command', async () => {
184
- mockExecSync.mockReset();
185
- const freshPublisher = new NpmPublisher(config);
186
- const testHelper = new NpmMockHelper(mockExecSync);
187
- mockFs.existsSync.mockReturnValue(true);
188
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '1.0.0' }));
189
- testHelper.mockPublishSuccess('@test/package', '1.0.0');
190
- await freshPublisher.publishPackage(testPackage);
191
- const publishCall = mockExecSync.mock.calls.find((call) => call[0].toString().includes('publish') && call[0].toString().includes('--access'));
192
- expect(publishCall).toBeDefined();
193
- expect(publishCall![0]).toContain('--access public');
194
- });
195
- });
196
-
197
- describe('publishPackages', () => {
198
- const packages: PackagePublish[] = [
199
- { name: '@test/package1', version: '1.0.0', path: '/test/path1', registry: 'https://registry.npmjs.org/', access: 'public' },
200
- { name: '@test/package2', version: '1.0.0', path: '/test/path2', registry: 'https://registry.npmjs.org/', access: 'public' },
201
- ];
202
-
203
- beforeEach(() => {
204
- mockFs.existsSync.mockReturnValue(true);
205
- mockFs.readFileSync.mockImplementation((path) => {
206
- if (path.toString().includes('path1')) return JSON.stringify({ name: '@test/package1', version: '1.0.0' });
207
- return JSON.stringify({ name: '@test/package2', version: '1.0.0' });
208
- });
209
- });
210
-
211
- it('should publish multiple packages successfully', async () => {
212
- mockExecSync.mockReset();
213
- const freshPublisher = new NpmPublisher(config);
214
- mockFs.existsSync.mockReturnValue(true);
215
- mockFs.readFileSync.mockImplementation((path) => {
216
- const pathStr = path.toString();
217
- if (pathStr.includes('path1')) return JSON.stringify({ name: '@test/package1', version: '1.0.0' });
218
- if (pathStr.includes('path2')) return JSON.stringify({ name: '@test/package2', version: '1.0.0' });
219
- return JSON.stringify({ name: '@test/unknown', version: '1.0.0' });
220
- });
221
- const testHelper = new NpmMockHelper(mockExecSync);
222
- testHelper.mockPublishSuccess('@test/package1', '1.0.0');
223
- mockExecSync.mockReturnValueOnce('' as any);
224
- mockExecSync.mockReturnValueOnce(`+ @test/package2@1.0.0\n` as any);
225
- const results = await freshPublisher.publishPackages(packages);
226
- expect(results).toHaveLength(2);
227
- expect(results[0].success).toBe(true);
228
- expect(results[1].success).toBe(true);
229
- });
230
-
231
- it('should stop publishing on first failure', async () => {
232
- jest.clearAllMocks();
233
- mockExecSync.mockReturnValueOnce('testuser\n' as any);
234
- mockExecSync.mockReturnValueOnce(JSON.stringify({ email: 'test@example.com' }) as any);
235
- mockFs.existsSync.mockReturnValue(true);
236
- mockFs.readFileSync.mockImplementation((path) => {
237
- if (path.toString().includes('package1')) return JSON.stringify({ name: '@test/package1', version: '1.0.0' });
238
- return JSON.stringify({ name: '@test/package2', version: '1.0.0' });
239
- });
240
- mockExecSync.mockReturnValueOnce('' as any);
241
- mockExecSync.mockImplementationOnce(() => { throw new Error('Publish failed'); });
242
- mockExecSync.mockImplementationOnce(() => { throw new Error('Publish failed'); });
243
- mockExecSync.mockImplementationOnce(() => { throw new Error('Publish failed'); });
244
- const results = await publisher.publishPackages(packages);
245
- expect(results).toHaveLength(2);
246
- expect(results[0].success).toBe(false);
247
- expect(results[1].success).toBe(false);
248
- expect(results[1].error).toContain('Publishing stopped due to previous failure');
249
- });
250
- });
251
-
252
- describe('unpublishPackage', () => {
253
- it('should unpublish package successfully', async () => {
254
- jest.clearAllMocks();
255
- const testHelper = new NpmMockHelper(mockExecSync);
256
- testHelper.mockUnpublish('@test/package', '1.0.0');
257
- const result = await publisher.unpublishPackage('@test/package', '1.0.0');
258
- expect(result).toBe(true);
259
- });
260
-
261
- it('should fail when package version does not exist', async () => {
262
- mockExecSync.mockReset();
263
- mockExecSync.mockReturnValueOnce('' as any);
264
- await expect(publisher.unpublishPackage('@test/package', '1.0.0')).rejects.toThrow('does not exist in registry');
265
- });
266
-
267
- it('should fail when unpublish command fails', async () => {
268
- mockExecSync.mockReset();
269
- const testHelper = new NpmMockHelper(mockExecSync);
270
- testHelper.mockUnpublishFailure('Unpublish failed', '@test/package', '1.0.0');
271
- await expect(publisher.unpublishPackage('@test/package', '1.0.0')).rejects.toThrow('Failed to unpublish package');
272
- });
273
- });
274
-
275
- describe('dry run mode', () => {
276
- it('should not actually publish in dry run mode', async () => {
277
- jest.clearAllMocks();
278
- const dryRunPublisher = new NpmPublisher({ ...config, dryRun: true });
279
- const dryRunHelper = new NpmMockHelper(mockExecSync);
280
- mockFs.existsSync.mockReturnValue(true);
281
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ name: '@test/package', version: '1.0.0' }));
282
- dryRunHelper.mockPublishSuccess('@test/package', '1.0.0');
283
- const result = await dryRunPublisher.publishPackage({ name: '@test/package', version: '1.0.0', path: '/test/path', registry: 'https://registry.npmjs.org/', access: 'public' });
284
- expect(result.success).toBe(true);
285
- const publishCall = mockExecSync.mock.calls.find((call) => call[0].toString().includes('publish'));
286
- expect(publishCall![0]).toContain('--dry-run');
287
- });
288
- });
289
- });
@@ -1,188 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Pipeline integration test: fixture summary docs → release notes with mocked git
4
- */
5
-
6
- import { SummaryScanner } from '../pipeline/SummaryScanner';
7
- import { ChangeExtractor } from '../pipeline/ChangeExtractor';
8
- import { ChangeClassifier } from '../pipeline/ChangeClassifier';
9
- import { NotesRenderer } from '../pipeline/NotesRenderer';
10
- import { ExtractedChange, ClassifiedChange } from '../types';
11
- import * as childProcess from 'child_process';
12
- import * as fs from 'fs';
13
-
14
- jest.mock('child_process');
15
- jest.mock('fs');
16
-
17
- const mockedExecSync = childProcess.execSync as jest.MockedFunction<typeof childProcess.execSync>;
18
- const mockedReadFileSync = fs.readFileSync as jest.MockedFunction<typeof fs.readFileSync>;
19
-
20
- // --- Fixture summary docs ---
21
-
22
- const FIXTURE_TOKEN_SUMMARY = `# Task 1 Summary: New Color Token
23
-
24
- **Date**: 2026-02-20
25
- **Type**: Implementation
26
- **Spec**: 070-color-tokens
27
-
28
- ## What Was Done
29
-
30
- Added a new semantic color token for error states.
31
-
32
- ## Key Changes
33
-
34
- - Created color.error.surface token
35
- - Updated theme definitions
36
-
37
- ## Why It Matters
38
-
39
- Consumers need a standard error surface color.
40
-
41
- ## Impact
42
-
43
- - ✅ All themes updated
44
- - ⚠️ Existing hardcoded error colors should migrate
45
-
46
- ## Deliverables *(optional)*
47
-
48
- - 🔴 Token: New color.error.surface semantic token
49
- - 🟡 Documentation: Updated token reference guide
50
- `;
51
-
52
- const FIXTURE_GOVERNANCE_SUMMARY = `# Task 2 Summary: Process Update
53
-
54
- **Date**: 2026-02-22
55
- **Type**: Documentation
56
- **Spec**: 071-governance
57
-
58
- ## What Was Done
59
-
60
- Updated the spec planning process with new template.
61
-
62
- ## Key Changes
63
-
64
- - Revised summary doc template
65
- - Added deliverables section guidance
66
-
67
- ## Why It Matters
68
-
69
- Improves consistency across specs.
70
-
71
- ## Impact
72
-
73
- - ✅ Template updated
74
- `;
75
-
76
- describe('Pipeline Integration', () => {
77
- beforeEach(() => {
78
- jest.useFakeTimers().setSystemTime(new Date('2026-02-27'));
79
- jest.clearAllMocks();
80
- });
81
-
82
- afterEach(() => {
83
- jest.useRealTimers();
84
- });
85
-
86
- const TAG_INFO = { tag: 'v7.0.0', commit: 'abc123', date: '2026-02-15' };
87
-
88
- it('should produce release notes from fixture summary docs end-to-end', async () => {
89
- // SummaryScanner: git log --diff-filter=A (encoding: utf-8 → returns string)
90
- mockedExecSync.mockReturnValueOnce(
91
- 'docs/specs/070-color-tokens/task-1-summary.md\ndocs/specs/071-governance/task-2-summary.md\n',
92
- );
93
-
94
- mockedReadFileSync
95
- .mockReturnValueOnce(FIXTURE_TOKEN_SUMMARY)
96
- .mockReturnValueOnce(FIXTURE_GOVERNANCE_SUMMARY);
97
-
98
- // --- Run pipeline stages ---
99
- const scanner = new SummaryScanner();
100
- const summaries = await scanner.findSummariesSinceTag(TAG_INFO);
101
-
102
- const extractor = new ChangeExtractor();
103
- const extracted: ExtractedChange[] = summaries
104
- .map((s) => extractor.extract(s))
105
- .filter((e): e is ExtractedChange => e !== undefined);
106
-
107
- const classifier = new ChangeClassifier();
108
- const classified: ClassifiedChange[] = classifier.classify(extracted);
109
-
110
- const renderer = new NotesRenderer();
111
- const notes = renderer.render({
112
- currentVersion: '7.0.0',
113
- recommendedVersion: '8.0.0',
114
- bumpType: 'major',
115
- rationale: 'Breaking changes detected',
116
- confidence: 0.9,
117
- changes: classified,
118
- });
119
-
120
- // --- Verify public notes ---
121
- expect(notes.public).toContain('# Release 8.0.0');
122
- expect(notes.public).toContain('🔴 Breaking / Consumer-Facing');
123
- expect(notes.public).toContain('New Color Token');
124
- // Context-only items excluded from public
125
- expect(notes.public).not.toContain('🔵 Internal');
126
-
127
- // --- Verify internal notes ---
128
- expect(notes.internal).toContain('New Color Token');
129
- expect(notes.internal).toContain('Process Update');
130
-
131
- // --- Verify JSON structure ---
132
- expect(notes.json.version).toBe('8.0.0');
133
- expect(notes.json.previousVersion).toBe('7.0.0');
134
- expect(notes.json.changes).toHaveLength(2);
135
- expect(notes.json.changes[0].priority).toBe('breaking');
136
- expect(notes.json.changes[1].priority).toBe('context');
137
- });
138
-
139
- it('should handle pipeline with no summaries found', async () => {
140
- mockedExecSync.mockReturnValueOnce('\n');
141
-
142
- const scanner = new SummaryScanner();
143
- const summaries = await scanner.findSummariesSinceTag(TAG_INFO);
144
-
145
- const extractor = new ChangeExtractor();
146
- const extracted = summaries
147
- .map((s) => extractor.extract(s))
148
- .filter((e): e is ExtractedChange => e !== undefined);
149
-
150
- const classifier = new ChangeClassifier();
151
- const classified = classifier.classify(extracted);
152
-
153
- const renderer = new NotesRenderer();
154
- const notes = renderer.render({
155
- currentVersion: '7.0.0',
156
- recommendedVersion: '7.0.0',
157
- bumpType: 'none',
158
- rationale: 'No changes',
159
- confidence: 1.0,
160
- changes: classified,
161
- });
162
-
163
- expect(notes.public).toContain('No consumer-facing changes');
164
- expect(notes.json.changes).toHaveLength(0);
165
- });
166
-
167
- it('should classify by keyword when deliverables are absent', async () => {
168
- mockedExecSync.mockReturnValueOnce('docs/specs/071-governance/task-2-summary.md\n');
169
-
170
- mockedReadFileSync.mockReturnValueOnce(FIXTURE_GOVERNANCE_SUMMARY);
171
-
172
- const scanner = new SummaryScanner();
173
- const summaries = await scanner.findSummariesSinceTag(TAG_INFO);
174
-
175
- const extractor = new ChangeExtractor();
176
- const extracted = summaries
177
- .map((s) => extractor.extract(s))
178
- .filter((e): e is ExtractedChange => e !== undefined);
179
-
180
- const classifier = new ChangeClassifier();
181
- const classified = classifier.classify(extracted);
182
-
183
- // "process" keyword → context
184
- expect(classified).toHaveLength(1);
185
- expect(classified[0].priority).toBe('context');
186
- expect(classified[0].deliverableType).toBe('Process');
187
- });
188
- });
@@ -1,192 +0,0 @@
1
- /**
2
- * @category evergreen
3
- * @purpose Verify ReleasePipeline orchestrates pipeline components correctly
4
- */
5
-
6
- import { ReleasePipeline } from '../cli/ReleasePipeline';
7
- import * as childProcess from 'child_process';
8
- import * as fs from 'fs';
9
-
10
- jest.mock('child_process');
11
- jest.mock('fs');
12
-
13
- const mockedExecSync = childProcess.execSync as jest.MockedFunction<typeof childProcess.execSync>;
14
- const mockedReadFileSync = fs.readFileSync as jest.MockedFunction<typeof fs.readFileSync>;
15
- const mockedExistsSync = fs.existsSync as jest.MockedFunction<typeof fs.existsSync>;
16
- const mockedWriteFileSync = fs.writeFileSync as jest.MockedFunction<typeof fs.writeFileSync>;
17
- const mockedMkdirSync = fs.mkdirSync as jest.MockedFunction<typeof fs.mkdirSync>;
18
- const mockedReaddirSync = fs.readdirSync as jest.MockedFunction<typeof fs.readdirSync>;
19
-
20
- const FIXTURE_SUMMARY = `# Task 1 Summary: New Token
21
-
22
- **Date**: 2026-02-20
23
- **Type**: Implementation
24
- **Spec**: 070-tokens
25
-
26
- ## What Was Done
27
-
28
- Added a new token.
29
-
30
- ## Key Changes
31
-
32
- - Created new token
33
-
34
- ## Why It Matters
35
-
36
- Consumers need it.
37
-
38
- ## Impact
39
-
40
- - ✅ Done
41
-
42
- ## Deliverables *(optional)*
43
-
44
- - 🔴 Token: New semantic token
45
- `;
46
-
47
- const CONFIG = JSON.stringify({ npmPublishEnabled: false, repoUrl: '', outputDir: 'docs/releases' });
48
-
49
- describe('ReleasePipeline', () => {
50
- beforeEach(() => {
51
- jest.useFakeTimers().setSystemTime(new Date('2026-02-27'));
52
- jest.clearAllMocks();
53
- // Default: config file read returns config
54
- mockedReadFileSync.mockImplementation((p: any) => {
55
- if (String(p).includes('release-config.json')) return CONFIG;
56
- return FIXTURE_SUMMARY;
57
- });
58
- mockedExistsSync.mockReturnValue(true);
59
- });
60
-
61
- afterEach(() => {
62
- jest.useRealTimers();
63
- });
64
-
65
- function mockTagAndSummaries(summaryPaths: string[]): void {
66
- // TagResolver: describe, rev-list, log (all return strings via encoding: utf-8)
67
- mockedExecSync
68
- .mockReturnValueOnce('v7.0.0\n')
69
- .mockReturnValueOnce('abc123\n')
70
- .mockReturnValueOnce('2026-02-15\n')
71
- // SummaryScanner: git log
72
- .mockReturnValueOnce(summaryPaths.join('\n') + '\n');
73
- }
74
-
75
- describe('analyze', () => {
76
- it('should return recommendation with changes', async () => {
77
- mockTagAndSummaries(['docs/specs/070-tokens/task-1-summary.md']);
78
- const pipeline = new ReleasePipeline();
79
- const rec = await pipeline.analyze();
80
-
81
- expect(rec.currentVersion).toBe('7.0.0');
82
- expect(rec.bumpType).toBe('major');
83
- expect(rec.changes).toHaveLength(1);
84
- expect(rec.changes[0].priority).toBe('breaking');
85
- });
86
-
87
- it('should return none bump when no summaries', async () => {
88
- mockTagAndSummaries([]);
89
- const pipeline = new ReleasePipeline();
90
- const rec = await pipeline.analyze();
91
-
92
- expect(rec.bumpType).toBe('none');
93
- expect(rec.changes).toHaveLength(0);
94
- });
95
- });
96
-
97
- describe('generateNotes', () => {
98
- it('should write files to output directory', async () => {
99
- mockTagAndSummaries(['docs/specs/070-tokens/task-1-summary.md']);
100
- const pipeline = new ReleasePipeline();
101
- await pipeline.generateNotes();
102
-
103
- expect(mockedMkdirSync).toHaveBeenCalled();
104
- expect(mockedWriteFileSync).toHaveBeenCalledTimes(3); // public, internal, json
105
- });
106
- });
107
-
108
- describe('checkAccumulation', () => {
109
- it('should warn when >5 files in output dir', () => {
110
- mockedReaddirSync.mockReturnValue(['a', 'b', 'c', 'd', 'e', 'f'] as any);
111
- const pipeline = new ReleasePipeline();
112
- const warning = pipeline.checkAccumulation();
113
-
114
- expect(warning).toContain('6 files');
115
- });
116
-
117
- it('should return null when <=5 files', () => {
118
- mockedReaddirSync.mockReturnValue(['a', 'b'] as any);
119
- const pipeline = new ReleasePipeline();
120
- expect(pipeline.checkAccumulation()).toBeNull();
121
- });
122
- });
123
-
124
- describe('release', () => {
125
- it('should return notes without tagging or publishing in dry-run mode', async () => {
126
- mockTagAndSummaries(['docs/specs/070-tokens/task-1-summary.md']);
127
- const pipeline = new ReleasePipeline();
128
- const result = await pipeline.release(true);
129
-
130
- expect(result.published).toBe(false);
131
- expect(result.notes.json.version).toBe('8.0.0');
132
- // generateNotes writes 3 files, but no tag creation call beyond that
133
- const execCalls = mockedExecSync.mock.calls;
134
- const tagCalls = execCalls.filter((c) => String(c[0]).includes('git tag'));
135
- expect(tagCalls).toHaveLength(0);
136
- });
137
-
138
- it('should create git tag when not dry-run', async () => {
139
- // First call: analyze (tag + scanner)
140
- mockTagAndSummaries(['docs/specs/070-tokens/task-1-summary.md']);
141
- // Second call inside release: generateNotes calls analyze again
142
- mockedExecSync
143
- .mockReturnValueOnce('v7.0.0\n')
144
- .mockReturnValueOnce('abc123\n')
145
- .mockReturnValueOnce('2026-02-15\n')
146
- .mockReturnValueOnce('docs/specs/070-tokens/task-1-summary.md\n')
147
- // createTag call
148
- .mockReturnValueOnce('');
149
-
150
- const pipeline = new ReleasePipeline();
151
- const result = await pipeline.release(false);
152
-
153
- const tagCalls = mockedExecSync.mock.calls.filter((c) => String(c[0]).includes('git tag'));
154
- expect(tagCalls).toHaveLength(1);
155
- expect(String(tagCalls[0][0])).toContain('v8.0.0');
156
- // No repoUrl configured, so not published
157
- expect(result.published).toBe(false);
158
- expect(result.error).toContain('No repoUrl configured');
159
- });
160
-
161
- it('should skip GitHub publish when GITHUB_TOKEN is not set', async () => {
162
- const configWithRepo = JSON.stringify({ npmPublishEnabled: false, repoUrl: 'https://github.com/3fn/DesignerPunk', outputDir: 'docs/releases' });
163
- mockedReadFileSync.mockImplementation((p: any) => {
164
- if (String(p).includes('release-config.json')) return configWithRepo;
165
- return FIXTURE_SUMMARY;
166
- });
167
-
168
- // analyze pass
169
- mockTagAndSummaries(['docs/specs/070-tokens/task-1-summary.md']);
170
- // generateNotes calls analyze again
171
- mockedExecSync
172
- .mockReturnValueOnce('v7.0.0\n')
173
- .mockReturnValueOnce('abc123\n')
174
- .mockReturnValueOnce('2026-02-15\n')
175
- .mockReturnValueOnce('docs/specs/070-tokens/task-1-summary.md\n')
176
- .mockReturnValueOnce(''); // createTag
177
-
178
- const originalToken = process.env.GITHUB_TOKEN;
179
- delete process.env.GITHUB_TOKEN;
180
-
181
- try {
182
- const pipeline = new ReleasePipeline();
183
- const result = await pipeline.release(false);
184
-
185
- expect(result.published).toBe(false);
186
- expect(result.error).toContain('GITHUB_TOKEN not set');
187
- } finally {
188
- if (originalToken) process.env.GITHUB_TOKEN = originalToken;
189
- }
190
- });
191
- });
192
- });