@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.
- package/.kiro/steering/Civitas-System-Overview.md +3 -12
- package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
- package/.kiro/steering/Task-Completion-Protocol.md +7 -7
- package/.kiro/steering/start-up-tasks.md +4 -4
- package/dist/ComponentTokens.android.kt +12 -12
- package/dist/ComponentTokens.ios.swift +12 -12
- package/dist/ComponentTokens.web.css +3 -3
- package/dist/DesignTokens.android.kt +1 -1
- package/dist/DesignTokens.dtcg.json +2 -2
- package/dist/DesignTokens.ios.swift +1 -1
- package/dist/DesignTokens.web.css +1 -1
- package/dist/android/DesignTokens.android.kt +1 -1
- package/dist/browser/designerpunk.esm.js +14 -9
- package/dist/browser/designerpunk.esm.min.js +7 -7
- package/dist/browser/designerpunk.umd.js +14 -9
- package/dist/browser/designerpunk.umd.min.js +12 -12
- package/dist/browser/tokens.css +3 -3
- package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
- package/dist/build/tokens/defineComponentTokens.js +26 -0
- package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
- package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
- package/dist/components/core/Avatar-Base/index.d.ts +1 -1
- package/dist/components/core/Avatar-Base/index.js +2 -2
- package/dist/components/core/Button-Icon/buttonIcon.tokens.d.ts +28 -14
- package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
- package/dist/generators/TokenFileGenerator.js +7 -2
- package/dist/ios/DesignTokens.ios.swift +1 -1
- package/dist/tokens/component/progress.d.ts +65 -5
- package/dist/tokens/component/progress.js +79 -18
- package/dist/types/generated/TokenTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.js +1 -1
- package/dist/web/DesignTokens.web.css +1 -1
- package/governance/BUILD-SYSTEM-SETUP.md +1 -2
- package/governance/Component-Development-Guide.md +1 -1
- package/governance/Component-Development-Standards.md +19 -18
- package/governance/Component-Family-Avatar.md +6 -6
- package/governance/Component-Family-Badge.md +19 -19
- package/governance/Component-Family-Button.md +26 -26
- package/governance/Component-Family-Chip.md +14 -14
- package/governance/Component-Family-Container.md +12 -12
- package/governance/Component-Family-Form-Inputs.md +61 -61
- package/governance/Component-Family-Icon.md +8 -8
- package/governance/Component-Family-Navigation.md +1 -1
- package/governance/Component-Inheritance-Structures.md +29 -28
- package/governance/Component-Readiness-Status.md +6 -7
- package/governance/Component-Templates.md +26 -26
- package/governance/Contract-System-Reference.md +1 -1
- package/governance/Process-Development-Workflow.md +4 -4
- package/governance/Process-File-Organization.md +1 -4
- package/governance/Process-Hook-Operations.md +6 -7
- package/governance/Process-Spec-Planning.md +8 -14
- package/governance/Rosetta-System-Architecture.md +7 -5
- package/governance/Token-Quick-Reference.md +35 -22
- package/governance/Web-Authoring-Standards.md +1 -1
- package/governance/classification-map.md +109 -3
- package/governance/completion-documentation-guide.md +14 -31
- package/governance/platform-implementation-guidelines.md +1 -2
- package/governance/release-management-system.md +28 -63
- package/package.json +2 -6
- package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
- package/src/build/tokens/defineComponentTokens.ts +43 -1
- package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
- package/src/components/core/Avatar-Base/index.ts +1 -1
- package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
- package/src/generators/TokenFileGenerator.ts +7 -2
- package/src/tokens/__tests__/ProgressTokenCompliance.test.ts +5 -3
- package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
- package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
- package/src/tokens/component/progress.ts +83 -21
- package/src/types/generated/TokenTypes.ts +1 -1
- package/token-index/components.yaml +8 -8
- package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
- package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
- package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
- package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
- package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
- package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
- package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
- package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
- package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
- package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
- package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
- package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
- package/src/tools/release/cli/ReleasePipeline.ts +0 -165
- package/src/tools/release/cli/release-tool.ts +0 -107
- package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
- package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
- package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
- package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
- package/src/tools/release/pipeline/TagResolver.ts +0 -40
- package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
- package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
- package/src/tools/release/publishers/NpmPublisher.ts +0 -196
- package/src/tools/release/release-config.json +0 -5
- package/src/tools/release/types/index.ts +0 -282
- 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
|
-
});
|