@3fn/core 13.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/agents/ada-prompt.md +80 -132
- package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
- package/.kiro/agents/ada.json +44 -59
- package/.kiro/agents/ada.json.attribution.json +13 -0
- package/.kiro/agents/data-prompt.md +83 -74
- package/.kiro/agents/data-prompt.md.attribution.json +53 -0
- package/.kiro/agents/data.json +31 -38
- package/.kiro/agents/data.json.attribution.json +13 -0
- package/.kiro/agents/kenya-prompt.md +83 -72
- package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
- package/.kiro/agents/kenya.json +27 -35
- package/.kiro/agents/kenya.json.attribution.json +13 -0
- package/.kiro/agents/leonardo-prompt.md +176 -234
- package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
- package/.kiro/agents/leonardo.json +28 -36
- package/.kiro/agents/leonardo.json.attribution.json +13 -0
- package/.kiro/agents/lina-prompt.md +110 -151
- package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
- package/.kiro/agents/lina.json +46 -59
- package/.kiro/agents/lina.json.attribution.json +13 -0
- package/.kiro/agents/sparky-prompt.md +89 -72
- package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
- package/.kiro/agents/sparky.json +37 -37
- package/.kiro/agents/sparky.json.attribution.json +13 -0
- package/.kiro/agents/stacy-prompt.md +74 -48
- package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
- package/.kiro/agents/stacy.json +28 -30
- package/.kiro/agents/stacy.json.attribution.json +13 -0
- package/.kiro/agents/thurgood-prompt.md +94 -138
- package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
- package/.kiro/agents/thurgood.json +31 -35
- package/.kiro/agents/thurgood.json.attribution.json +13 -0
- package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
- package/.kiro/steering/Civitas-System-Overview.md +7 -16
- package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
- package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
- package/.kiro/steering/Task-Completion-Protocol.md +98 -17
- package/.kiro/steering/core-goals.md +3 -3
- package/.kiro/steering/personal-note.md +1 -1
- package/.kiro/steering/start-up-tasks.md +17 -6
- package/application-mcp-server/src/index.ts +26 -0
- 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 +8 -5
- package/dist/DesignTokens.figma.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/blend/OklchBlendCalculator.js +1 -0
- package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
- package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
- package/dist/browser/designerpunk.esm.js +36 -90
- package/dist/browser/designerpunk.esm.min.js +33 -36
- package/dist/browser/designerpunk.umd.js +36 -90
- package/dist/browser/designerpunk.umd.min.js +47 -50
- 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/Avatar-Base/platforms/web/Avatar.web.js +24 -5
- package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
- package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
- package/dist/components/core/Button-CTA/types.d.ts +0 -24
- package/dist/components/core/Button-CTA/types.js +6 -0
- 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/components/core/Input-Text-Base/types.d.ts +13 -1
- package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
- package/dist/generators/DTCGFormatGenerator.js +8 -0
- package/dist/generators/TokenFileGenerator.js +7 -2
- package/dist/integration/BuildErrorHandler.js +2 -2
- package/dist/ios/DesignTokens.ios.swift +1 -1
- package/dist/mcp/application-mcp.js +24 -0
- package/dist/mcp/docs-mcp.js +130 -15
- package/dist/mcp/product-mcp.js +25 -0
- package/dist/tokens/OpacityTokens.js +1 -1
- package/dist/tokens/component/progress.d.ts +65 -5
- package/dist/tokens/component/progress.js +79 -18
- package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
- package/dist/tokens/semantic/BlendTokens.js +17 -5
- package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
- package/dist/tokens/semantic/OpacityTokens.js +4 -4
- package/dist/types/ComponentTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.js +1 -1
- package/dist/validators/StemmaTokenUsageValidator.js +3 -2
- package/dist/web/DesignTokens.web.css +1 -1
- package/governance/BUILD-SYSTEM-SETUP.md +1 -2
- package/governance/Component-Development-Guide.md +23 -13
- package/governance/Component-Development-Standards.md +21 -20
- package/governance/Component-Family-Avatar.md +6 -7
- package/governance/Component-Family-Badge.md +19 -20
- package/governance/Component-Family-Button.md +30 -43
- package/governance/Component-Family-Chip.md +14 -15
- package/governance/Component-Family-Container.md +12 -13
- package/governance/Component-Family-Data-Display.md +1 -2
- package/governance/Component-Family-Divider.md +1 -2
- package/governance/Component-Family-Form-Inputs.md +65 -65
- package/governance/Component-Family-Icon.md +9 -10
- package/governance/Component-Family-Loading.md +1 -2
- package/governance/Component-Family-Modal.md +1 -2
- package/governance/Component-Family-Navigation.md +1 -2
- package/governance/Component-Family-Progress.md +0 -1
- package/governance/Component-Inheritance-Structures.md +219 -99
- package/governance/Component-MCP-Document-Template.md +6 -5
- package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
- package/governance/Component-Quick-Reference.md +33 -33
- package/governance/Component-Readiness-Status.md +59 -44
- package/governance/Component-Templates.md +57 -61
- package/governance/Contract-System-Reference.md +7 -7
- package/governance/MCP-Integration-Guide.md +1 -1
- package/governance/Process-Cross-Reference-Standards.md +31 -13
- package/governance/Process-Development-Workflow.md +49 -59
- package/governance/Process-File-Organization.md +25 -28
- package/governance/Process-Hook-Operations.md +22 -11
- package/governance/Process-Orchestration-Model-Selection.md +92 -0
- package/governance/Process-Spec-Planning.md +98 -52
- package/governance/Process-Task-Type-Definitions.md +80 -4
- package/governance/Product-Handoff-Protocol.md +2 -0
- package/governance/Rosetta-System-Architecture.md +13 -11
- package/governance/Test-Behavioral-Contract-Validation.md +38 -31
- package/governance/Test-Failure-Audit-Methodology.md +1 -1
- package/governance/Token-Family-Accessibility.md +1 -2
- package/governance/Token-Family-Blend.md +18 -16
- package/governance/Token-Family-Blur.md +0 -1
- package/governance/Token-Family-Border.md +1 -2
- package/governance/Token-Family-Color.md +0 -1
- package/governance/Token-Family-Glow.md +1 -2
- package/governance/Token-Family-Layering.md +0 -1
- package/governance/Token-Family-Motion.md +1 -2
- package/governance/Token-Family-Opacity.md +0 -1
- package/governance/Token-Family-Radius.md +1 -2
- package/governance/Token-Family-Responsive.md +1 -2
- package/governance/Token-Family-Shadow.md +1 -2
- package/governance/Token-Family-Sizing.md +0 -1
- package/governance/Token-Family-Spacing.md +1 -2
- package/governance/Token-Family-Typography.md +1 -2
- package/governance/Token-Governance.md +8 -8
- package/governance/Token-Quick-Reference.md +47 -34
- package/governance/Token-Resolution-Patterns.md +1 -1
- package/governance/Token-Semantic-Structure.md +1 -1
- package/governance/Web-Authoring-Standards.md +5 -5
- package/governance/browser-distribution-guide.md +1 -4
- package/governance/classification-map.md +474 -0
- package/governance/completion-documentation-guide.md +23 -37
- package/governance/component-meta-authoring-guide.md +1 -1
- package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
- package/governance/platform-implementation-guidelines.md +2 -3
- package/governance/release-management-system.md +28 -63
- package/governance/rosetta-system-principles.md +8 -6
- package/governance/stemma-system-principles.md +18 -17
- package/mcp-server/src/index.ts +24 -6
- package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
- package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
- package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
- package/mcp-server/src/indexer/index-health.ts +27 -2
- package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
- package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
- package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
- package/mcp-server/src/tools/list-cross-references.ts +2 -2
- package/package.json +24 -26
- package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
- package/src/__tests__/console-allowlist.json +14 -0
- package/src/__tests__/console-fail-setup.ts +169 -0
- package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
- package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
- package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
- package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
- package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
- package/src/blend/OklchBlendCalculator.ts +3 -0
- package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
- package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
- package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
- package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
- package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
- package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
- package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
- package/src/build/tokens/defineComponentTokens.ts +43 -1
- package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
- package/src/cli/__tests__/init.test.ts +45 -11
- package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
- package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
- package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
- package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
- package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
- package/src/components/core/Avatar-Base/contracts.yaml +11 -1
- package/src/components/core/Avatar-Base/index.ts +1 -1
- package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
- package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
- package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
- package/src/components/core/Button-CTA/README.md +3 -6
- package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
- package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
- package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
- package/src/components/core/Button-CTA/contracts.yaml +6 -29
- package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
- package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
- package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
- package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
- package/src/components/core/Button-CTA/types.ts +10 -28
- package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
- package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
- package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
- package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
- package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
- package/src/components/core/Input-Text-Base/README.md +25 -2
- package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
- package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
- package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
- package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
- package/src/components/core/Input-Text-Base/types.ts +13 -1
- package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
- package/src/components/core/Input-Text-Email/README.md +8 -7
- package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
- package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
- package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
- package/src/components/core/Input-Text-Password/README.md +9 -8
- package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
- package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
- package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
- package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
- package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
- package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
- package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
- package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
- package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
- package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
- package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
- package/src/generators/DTCGFormatGenerator.ts +6 -0
- package/src/generators/TokenFileGenerator.ts +7 -2
- package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
- package/src/integration/BuildErrorHandler.ts +2 -2
- package/src/tokens/OpacityTokens.ts +1 -1
- package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
- 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/tokens/semantic/BlendTokens.ts +26 -5
- package/src/tokens/semantic/OpacityTokens.ts +4 -4
- package/src/types/ComponentTypes.ts +1 -1
- package/src/types/generated/TokenTypes.ts +1 -1
- package/src/validators/StemmaTokenUsageValidator.ts +3 -2
- package/token-index/components.yaml +8 -8
- package/token-index/semantics.yaml +1 -2
- 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,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @category evergreen
|
|
3
|
-
* @purpose Verify ChangeExtractor parses summary docs correctly
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { ChangeExtractor } from '../pipeline/ChangeExtractor';
|
|
7
|
-
import { SummaryDoc } from '../types';
|
|
8
|
-
|
|
9
|
-
describe('ChangeExtractor', () => {
|
|
10
|
-
let extractor: ChangeExtractor;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
extractor = new ChangeExtractor();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const makeSummaryDoc = (raw: string, overrides: Partial<SummaryDoc> = {}): SummaryDoc => ({
|
|
17
|
-
path: 'docs/specs/065-release-system-rebuild/task-1-summary.md',
|
|
18
|
-
specName: '065-release-system-rebuild',
|
|
19
|
-
taskNumber: 1,
|
|
20
|
-
raw,
|
|
21
|
-
...overrides,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const fullDoc = makeSummaryDoc(`# Task 1 Summary: Summary Format Enhancement
|
|
25
|
-
|
|
26
|
-
**Date**: 2026-02-27
|
|
27
|
-
**Spec**: 065-release-system-rebuild
|
|
28
|
-
**Type**: Architecture
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## What Was Done
|
|
33
|
-
|
|
34
|
-
Added an optional Deliverables section to the summary template.
|
|
35
|
-
|
|
36
|
-
## Why It Matters
|
|
37
|
-
|
|
38
|
-
The release tool needs structured deliverable classification.
|
|
39
|
-
|
|
40
|
-
## Key Changes
|
|
41
|
-
|
|
42
|
-
- Added Deliverables section to summary template
|
|
43
|
-
- Added guidance note explaining three tiers
|
|
44
|
-
- Updated example summary
|
|
45
|
-
|
|
46
|
-
## Impact
|
|
47
|
-
|
|
48
|
-
- ✅ Release tool gets structured data
|
|
49
|
-
- ✅ Fallback to section-based extraction when absent
|
|
50
|
-
- ✅ Minimal per-task overhead
|
|
51
|
-
|
|
52
|
-
## Deliverables *(optional)*
|
|
53
|
-
|
|
54
|
-
- 🔵 Governance: Deliverables field added to summary template
|
|
55
|
-
- 🟡 Tool: Release tool scaffold created
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
*For detailed notes, see completion doc*
|
|
60
|
-
`);
|
|
61
|
-
|
|
62
|
-
describe('full document parsing', () => {
|
|
63
|
-
it('should extract all sections', () => {
|
|
64
|
-
const result = extractor.extract(fullDoc);
|
|
65
|
-
|
|
66
|
-
expect(result.specName).toBe('065-release-system-rebuild');
|
|
67
|
-
expect(result.taskTitle).toBe('Summary Format Enhancement');
|
|
68
|
-
expect(result.taskType).toBe('Architecture');
|
|
69
|
-
expect(result.whatWasDone).toContain('Added an optional Deliverables section');
|
|
70
|
-
expect(result.whyItMatters).toContain('structured deliverable classification');
|
|
71
|
-
expect(result.keyChanges).toHaveLength(3);
|
|
72
|
-
expect(result.keyChanges[0]).toBe('Added Deliverables section to summary template');
|
|
73
|
-
expect(result.impact).toHaveLength(3);
|
|
74
|
-
expect(result.impact[0]).toBe('Release tool gets structured data');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should extract deliverables with correct priority and type', () => {
|
|
78
|
-
const result = extractor.extract(fullDoc);
|
|
79
|
-
|
|
80
|
-
expect(result.deliverables).toHaveLength(2);
|
|
81
|
-
expect(result.deliverables![0]).toEqual({
|
|
82
|
-
priority: '🔵',
|
|
83
|
-
type: 'Governance',
|
|
84
|
-
description: 'Deliverables field added to summary template',
|
|
85
|
-
});
|
|
86
|
-
expect(result.deliverables![1]).toEqual({
|
|
87
|
-
priority: '🟡',
|
|
88
|
-
type: 'Tool',
|
|
89
|
-
description: 'Release tool scaffold created',
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('document without deliverables', () => {
|
|
95
|
-
it('should return undefined deliverables when section missing', () => {
|
|
96
|
-
const doc = makeSummaryDoc(`# Task 2 Summary: Some Task
|
|
97
|
-
|
|
98
|
-
**Type**: Implementation
|
|
99
|
-
|
|
100
|
-
## What Was Done
|
|
101
|
-
|
|
102
|
-
Did something useful.
|
|
103
|
-
|
|
104
|
-
## Why It Matters
|
|
105
|
-
|
|
106
|
-
It matters a lot.
|
|
107
|
-
|
|
108
|
-
## Key Changes
|
|
109
|
-
|
|
110
|
-
- Change one
|
|
111
|
-
- Change two
|
|
112
|
-
|
|
113
|
-
## Impact
|
|
114
|
-
|
|
115
|
-
- ✅ Good outcome
|
|
116
|
-
`);
|
|
117
|
-
|
|
118
|
-
const result = extractor.extract(doc);
|
|
119
|
-
|
|
120
|
-
expect(result.deliverables).toBeUndefined();
|
|
121
|
-
expect(result.taskTitle).toBe('Some Task');
|
|
122
|
-
expect(result.taskType).toBe('Implementation');
|
|
123
|
-
expect(result.keyChanges).toHaveLength(2);
|
|
124
|
-
expect(result.impact).toHaveLength(1);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('missing sections', () => {
|
|
129
|
-
it('should handle minimal document gracefully', () => {
|
|
130
|
-
const doc = makeSummaryDoc(`# Task 3 Summary: Minimal
|
|
131
|
-
|
|
132
|
-
**Type**: Setup
|
|
133
|
-
|
|
134
|
-
## What Was Done
|
|
135
|
-
|
|
136
|
-
Just this.
|
|
137
|
-
`);
|
|
138
|
-
|
|
139
|
-
const result = extractor.extract(doc);
|
|
140
|
-
|
|
141
|
-
expect(result.taskTitle).toBe('Minimal');
|
|
142
|
-
expect(result.taskType).toBe('Setup');
|
|
143
|
-
expect(result.whatWasDone).toBe('Just this.');
|
|
144
|
-
expect(result.whyItMatters).toBe('');
|
|
145
|
-
expect(result.keyChanges).toHaveLength(0);
|
|
146
|
-
expect(result.impact).toHaveLength(0);
|
|
147
|
-
expect(result.deliverables).toBeUndefined();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should handle empty document', () => {
|
|
151
|
-
const doc = makeSummaryDoc('');
|
|
152
|
-
|
|
153
|
-
const result = extractor.extract(doc);
|
|
154
|
-
|
|
155
|
-
expect(result.taskTitle).toBe('');
|
|
156
|
-
expect(result.taskType).toBe('Unknown');
|
|
157
|
-
expect(result.whatWasDone).toBe('');
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('deliverable parsing edge cases', () => {
|
|
162
|
-
it('should parse all three priority levels', () => {
|
|
163
|
-
const doc = makeSummaryDoc(`# Task 4 Summary: Multi-tier
|
|
164
|
-
|
|
165
|
-
**Type**: Architecture
|
|
166
|
-
|
|
167
|
-
## Deliverables *(optional)*
|
|
168
|
-
|
|
169
|
-
- 🔴 Component: New ButtonCTA component
|
|
170
|
-
- 🟡 Build: Updated build pipeline
|
|
171
|
-
- 🔵 Governance: Process documentation
|
|
172
|
-
`);
|
|
173
|
-
|
|
174
|
-
const result = extractor.extract(doc);
|
|
175
|
-
|
|
176
|
-
expect(result.deliverables).toHaveLength(3);
|
|
177
|
-
expect(result.deliverables![0].priority).toBe('🔴');
|
|
178
|
-
expect(result.deliverables![1].priority).toBe('🟡');
|
|
179
|
-
expect(result.deliverables![2].priority).toBe('🔵');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('should skip malformed deliverable lines', () => {
|
|
183
|
-
const doc = makeSummaryDoc(`# Task 5 Summary: Mixed
|
|
184
|
-
|
|
185
|
-
**Type**: Implementation
|
|
186
|
-
|
|
187
|
-
## Deliverables *(optional)*
|
|
188
|
-
|
|
189
|
-
- 🔴 Token: Valid entry
|
|
190
|
-
- This is not a deliverable
|
|
191
|
-
- 🟡 Tool: Another valid entry
|
|
192
|
-
`);
|
|
193
|
-
|
|
194
|
-
const result = extractor.extract(doc);
|
|
195
|
-
|
|
196
|
-
expect(result.deliverables).toHaveLength(2);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe('impact line cleaning', () => {
|
|
201
|
-
it('should strip emoji prefixes from impact lines', () => {
|
|
202
|
-
const doc = makeSummaryDoc(`# Task 6 Summary: Emoji
|
|
203
|
-
|
|
204
|
-
**Type**: Implementation
|
|
205
|
-
|
|
206
|
-
## Impact
|
|
207
|
-
|
|
208
|
-
- ✅ Good thing happened
|
|
209
|
-
- ⚠️ Watch out for this
|
|
210
|
-
- ❌ This broke
|
|
211
|
-
`);
|
|
212
|
-
|
|
213
|
-
const result = extractor.extract(doc);
|
|
214
|
-
|
|
215
|
-
expect(result.impact).toEqual([
|
|
216
|
-
'Good thing happened',
|
|
217
|
-
'Watch out for this',
|
|
218
|
-
'This broke',
|
|
219
|
-
]);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
});
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @category evergreen
|
|
3
|
-
* @purpose Verify GitHub publisher handles package publishing correctly
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { GitHubPublisher, GitHubConfig } from '../publishers/GitHubPublisher';
|
|
7
|
-
import { GitHubRelease, GitTag, Artifact } from '../types';
|
|
8
|
-
import * as fs from 'fs';
|
|
9
|
-
|
|
10
|
-
jest.mock('@octokit/rest');
|
|
11
|
-
const { Octokit } = require('@octokit/rest');
|
|
12
|
-
|
|
13
|
-
jest.mock('fs');
|
|
14
|
-
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
15
|
-
|
|
16
|
-
describe('GitHubPublisher', () => {
|
|
17
|
-
let publisher: GitHubPublisher;
|
|
18
|
-
let mockOctokit: any;
|
|
19
|
-
let config: GitHubConfig;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
jest.clearAllMocks();
|
|
23
|
-
mockOctokit = {
|
|
24
|
-
rest: {
|
|
25
|
-
users: { getAuthenticated: jest.fn() },
|
|
26
|
-
repos: { get: jest.fn(), getReleaseByTag: jest.fn(), createRelease: jest.fn(), deleteRelease: jest.fn(), uploadReleaseAsset: jest.fn() },
|
|
27
|
-
git: { getCommit: jest.fn(), createTag: jest.fn(), createRef: jest.fn(), deleteRef: jest.fn() },
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
Octokit.mockImplementation(() => mockOctokit);
|
|
31
|
-
config = { token: 'test-token', owner: 'test-owner', repo: 'test-repo' };
|
|
32
|
-
publisher = new GitHubPublisher(config);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('Authentication', () => {
|
|
36
|
-
it('should validate authentication successfully', async () => {
|
|
37
|
-
mockOctokit.rest.users.getAuthenticated.mockResolvedValue({ data: { login: 'test-user' } });
|
|
38
|
-
const result = await publisher.validateAuthentication();
|
|
39
|
-
expect(result).toBe(true);
|
|
40
|
-
expect(mockOctokit.rest.users.getAuthenticated).toHaveBeenCalled();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should throw error on authentication failure', async () => {
|
|
44
|
-
mockOctokit.rest.users.getAuthenticated.mockRejectedValue(new Error('Invalid token'));
|
|
45
|
-
await expect(publisher.validateAuthentication()).rejects.toThrow('GitHub authentication failed');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('Repository Information', () => {
|
|
50
|
-
it('should get repository information', async () => {
|
|
51
|
-
mockOctokit.rest.repos.get.mockResolvedValue({
|
|
52
|
-
data: { owner: { login: 'test-owner' }, name: 'test-repo', full_name: 'test-owner/test-repo', default_branch: 'main', private: false, html_url: 'https://github.com/test-owner/test-repo' },
|
|
53
|
-
});
|
|
54
|
-
const info = await publisher.getRepositoryInfo();
|
|
55
|
-
expect(info).toEqual({ owner: 'test-owner', name: 'test-repo', fullName: 'test-owner/test-repo', defaultBranch: 'main', private: false, url: 'https://github.com/test-owner/test-repo' });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should throw error when repository not found', async () => {
|
|
59
|
-
mockOctokit.rest.repos.get.mockRejectedValue(new Error('Not Found'));
|
|
60
|
-
await expect(publisher.getRepositoryInfo()).rejects.toThrow('Failed to get repository info');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('Release Existence Check', () => {
|
|
65
|
-
it('should return true when release exists', async () => {
|
|
66
|
-
mockOctokit.rest.repos.getReleaseByTag.mockResolvedValue({ data: { id: 123, tag_name: 'v1.0.0' } });
|
|
67
|
-
expect(await publisher.releaseExists('v1.0.0')).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should return false when release does not exist', async () => {
|
|
71
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue({ status: 404, message: 'Not Found' });
|
|
72
|
-
expect(await publisher.releaseExists('v1.0.0')).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should throw error on non-404 errors', async () => {
|
|
76
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue(new Error('Server error'));
|
|
77
|
-
await expect(publisher.releaseExists('v1.0.0')).rejects.toThrow('Failed to check if release exists');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('Tag Creation', () => {
|
|
82
|
-
it('should create tags successfully', async () => {
|
|
83
|
-
const tags: GitTag[] = [{ name: 'v1.0.0', message: 'Release v1.0.0', commit: 'abc123' }];
|
|
84
|
-
mockOctokit.rest.git.getCommit.mockResolvedValue({ data: { sha: 'abc123' } });
|
|
85
|
-
mockOctokit.rest.git.createTag.mockResolvedValue({ data: { sha: 'def456' } });
|
|
86
|
-
mockOctokit.rest.git.createRef.mockResolvedValue({ data: { ref: 'refs/tags/v1.0.0' } });
|
|
87
|
-
const results = await publisher.createTags(tags);
|
|
88
|
-
expect(results).toHaveLength(1);
|
|
89
|
-
expect(results[0]).toEqual({ success: true, tagName: 'v1.0.0' });
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle tag creation failures', async () => {
|
|
93
|
-
mockOctokit.rest.git.getCommit.mockRejectedValue(new Error('Commit not found'));
|
|
94
|
-
const results = await publisher.createTags([{ name: 'v1.0.0', message: 'Release v1.0.0', commit: 'abc123' }]);
|
|
95
|
-
expect(results[0]).toEqual({ success: false, tagName: 'v1.0.0', error: 'Commit not found' });
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should create multiple tags', async () => {
|
|
99
|
-
mockOctokit.rest.git.getCommit.mockResolvedValue({ data: { sha: 'abc123' } });
|
|
100
|
-
mockOctokit.rest.git.createTag.mockResolvedValue({ data: { sha: 'tag-sha' } });
|
|
101
|
-
mockOctokit.rest.git.createRef.mockResolvedValue({ data: { ref: 'refs/tags/v1.0.0' } });
|
|
102
|
-
const results = await publisher.createTags([
|
|
103
|
-
{ name: 'v1.0.0', message: 'Release v1.0.0', commit: 'abc123' },
|
|
104
|
-
{ name: 'v1.0.1', message: 'Release v1.0.1', commit: 'def456' },
|
|
105
|
-
]);
|
|
106
|
-
expect(results).toHaveLength(2);
|
|
107
|
-
expect(results.every((r) => r.success)).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('Release Creation', () => {
|
|
112
|
-
beforeEach(() => {
|
|
113
|
-
mockOctokit.rest.users.getAuthenticated.mockResolvedValue({ data: { login: 'test-user' } });
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should create release successfully', async () => {
|
|
117
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue({ status: 404 });
|
|
118
|
-
mockOctokit.rest.repos.createRelease.mockResolvedValue({
|
|
119
|
-
data: { id: 123, tag_name: 'v1.0.0', html_url: 'https://github.com/test-owner/test-repo/releases/tag/v1.0.0' },
|
|
120
|
-
});
|
|
121
|
-
const result = await publisher.createRelease({ tagName: 'v1.0.0', name: 'Release v1.0.0', body: '## Changes\n- Feature 1', draft: false, prerelease: false, artifacts: [] });
|
|
122
|
-
expect(result.success).toBe(true);
|
|
123
|
-
expect(result.version).toBe('v1.0.0');
|
|
124
|
-
expect(result.githubReleaseUrl).toBe('https://github.com/test-owner/test-repo/releases/tag/v1.0.0');
|
|
125
|
-
expect(result.errors).toHaveLength(0);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should fail when release already exists', async () => {
|
|
129
|
-
mockOctokit.rest.repos.getReleaseByTag.mockResolvedValue({ data: { id: 123 } });
|
|
130
|
-
const result = await publisher.createRelease({ tagName: 'v1.0.0', name: 'Release v1.0.0', body: 'Release notes', draft: false, prerelease: false, artifacts: [] });
|
|
131
|
-
expect(result.success).toBe(false);
|
|
132
|
-
expect(result.errors[0].message).toContain('already exists');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should create draft release', async () => {
|
|
136
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue({ status: 404 });
|
|
137
|
-
mockOctokit.rest.repos.createRelease.mockResolvedValue({ data: { id: 123, tag_name: 'v1.0.0', html_url: 'https://github.com/test-owner/test-repo/releases/tag/v1.0.0' } });
|
|
138
|
-
const result = await publisher.createRelease({ tagName: 'v1.0.0', name: 'Release v1.0.0', body: 'Release notes', draft: true, prerelease: false, artifacts: [] });
|
|
139
|
-
expect(result.success).toBe(true);
|
|
140
|
-
expect(mockOctokit.rest.repos.createRelease).toHaveBeenCalledWith(expect.objectContaining({ draft: true }));
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should create prerelease', async () => {
|
|
144
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue({ status: 404 });
|
|
145
|
-
mockOctokit.rest.repos.createRelease.mockResolvedValue({ data: { id: 123, tag_name: 'v1.0.0-beta.1', html_url: 'https://github.com/test-owner/test-repo/releases/tag/v1.0.0-beta.1' } });
|
|
146
|
-
const result = await publisher.createRelease({ tagName: 'v1.0.0-beta.1', name: 'Release v1.0.0-beta.1', body: 'Beta release', draft: false, prerelease: true, artifacts: [] });
|
|
147
|
-
expect(result.success).toBe(true);
|
|
148
|
-
expect(mockOctokit.rest.repos.createRelease).toHaveBeenCalledWith(expect.objectContaining({ prerelease: true }));
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should handle release creation errors', async () => {
|
|
152
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue({ status: 404 });
|
|
153
|
-
mockOctokit.rest.repos.createRelease.mockRejectedValue(new Error('API error'));
|
|
154
|
-
const result = await publisher.createRelease({ tagName: 'v1.0.0', name: 'Release v1.0.0', body: 'Release notes', draft: false, prerelease: false, artifacts: [] });
|
|
155
|
-
expect(result.success).toBe(false);
|
|
156
|
-
expect(result.errors[0].code).toBe('RELEASE_CREATION_FAILED');
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe('Artifact Upload', () => {
|
|
161
|
-
beforeEach(() => { jest.clearAllMocks(); });
|
|
162
|
-
|
|
163
|
-
it('should upload artifacts successfully', async () => {
|
|
164
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
165
|
-
mockFs.readFileSync.mockReturnValue(Buffer.from('test content'));
|
|
166
|
-
mockOctokit.rest.repos.uploadReleaseAsset.mockResolvedValue({ data: { browser_download_url: 'https://github.com/test-owner/test-repo/releases/download/v1.0.0/build.zip' } });
|
|
167
|
-
const results = await publisher.uploadArtifacts('123', [{ name: 'build.zip', path: '/path/to/build.zip', contentType: 'application/zip', size: 1024 }]);
|
|
168
|
-
expect(results).toHaveLength(1);
|
|
169
|
-
expect(results[0]).toEqual({ success: true, artifactName: 'build.zip', url: 'https://github.com/test-owner/test-repo/releases/download/v1.0.0/build.zip' });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should handle missing artifact files', async () => {
|
|
173
|
-
mockFs.existsSync.mockReturnValue(false);
|
|
174
|
-
const results = await publisher.uploadArtifacts('123', [{ name: 'missing.zip', path: '/path/to/missing.zip', contentType: 'application/zip', size: 1024 }]);
|
|
175
|
-
expect(results[0]).toEqual({ success: false, artifactName: 'missing.zip', error: expect.stringContaining('not found') });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should handle upload failures', async () => {
|
|
179
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
180
|
-
mockFs.readFileSync.mockReturnValue(Buffer.from('test content'));
|
|
181
|
-
mockOctokit.rest.repos.uploadReleaseAsset.mockRejectedValue(new Error('Upload failed'));
|
|
182
|
-
const results = await publisher.uploadArtifacts('123', [{ name: 'build.zip', path: '/path/to/build.zip', contentType: 'application/zip', size: 1024 }]);
|
|
183
|
-
expect(results[0]).toEqual({ success: false, artifactName: 'build.zip', error: 'Upload failed' });
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should upload multiple artifacts', async () => {
|
|
187
|
-
mockFs.existsSync.mockReturnValue(true);
|
|
188
|
-
mockFs.readFileSync.mockReturnValue(Buffer.from('test content'));
|
|
189
|
-
mockOctokit.rest.repos.uploadReleaseAsset.mockResolvedValue({ data: { browser_download_url: 'https://github.com/test-owner/test-repo/releases/download/v1.0.0/build.zip' } });
|
|
190
|
-
const results = await publisher.uploadArtifacts('123', [
|
|
191
|
-
{ name: 'build1.zip', path: '/path/to/build1.zip', contentType: 'application/zip', size: 1024 },
|
|
192
|
-
{ name: 'build2.zip', path: '/path/to/build2.zip', contentType: 'application/zip', size: 2048 },
|
|
193
|
-
]);
|
|
194
|
-
expect(results).toHaveLength(2);
|
|
195
|
-
expect(results.every((r) => r.success)).toBe(true);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe('Rollback Operations', () => {
|
|
200
|
-
it('should delete release successfully', async () => {
|
|
201
|
-
mockOctokit.rest.repos.getReleaseByTag.mockResolvedValue({ data: { id: 123 } });
|
|
202
|
-
mockOctokit.rest.repos.deleteRelease.mockResolvedValue({});
|
|
203
|
-
expect(await publisher.deleteRelease('v1.0.0')).toBe(true);
|
|
204
|
-
expect(mockOctokit.rest.repos.deleteRelease).toHaveBeenCalledWith({ owner: 'test-owner', repo: 'test-repo', release_id: 123 });
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should handle release deletion errors', async () => {
|
|
208
|
-
mockOctokit.rest.repos.getReleaseByTag.mockRejectedValue(new Error('Release not found'));
|
|
209
|
-
await expect(publisher.deleteRelease('v1.0.0')).rejects.toThrow('Failed to delete release');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should delete tag successfully', async () => {
|
|
213
|
-
mockOctokit.rest.git.deleteRef.mockResolvedValue({});
|
|
214
|
-
expect(await publisher.deleteTag('v1.0.0')).toBe(true);
|
|
215
|
-
expect(mockOctokit.rest.git.deleteRef).toHaveBeenCalledWith({ owner: 'test-owner', repo: 'test-repo', ref: 'tags/v1.0.0' });
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should handle tag deletion errors', async () => {
|
|
219
|
-
mockOctokit.rest.git.deleteRef.mockRejectedValue(new Error('Tag not found'));
|
|
220
|
-
await expect(publisher.deleteTag('v1.0.0')).rejects.toThrow('Failed to delete tag');
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe('Configuration', () => {
|
|
225
|
-
it('should use default timeout', () => {
|
|
226
|
-
new GitHubPublisher(config);
|
|
227
|
-
expect(Octokit).toHaveBeenCalledWith(expect.objectContaining({ request: expect.objectContaining({ timeout: 30000 }) }));
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should use custom timeout', () => {
|
|
231
|
-
new GitHubPublisher({ ...config, timeout: 60000 });
|
|
232
|
-
expect(Octokit).toHaveBeenCalledWith(expect.objectContaining({ request: expect.objectContaining({ timeout: 60000 }) }));
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('should use custom base URL for GitHub Enterprise', () => {
|
|
236
|
-
new GitHubPublisher({ ...config, baseUrl: 'https://github.enterprise.com/api/v3' });
|
|
237
|
-
expect(Octokit).toHaveBeenCalledWith(expect.objectContaining({ baseUrl: 'https://github.enterprise.com/api/v3' }));
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @category evergreen
|
|
3
|
-
* @purpose Verify NotesRenderer generates correct markdown and JSON output
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { NotesRenderer } from '../pipeline/NotesRenderer';
|
|
7
|
-
import { PipelineRecommendation, ClassifiedChange, ExtractedChange } from '../types';
|
|
8
|
-
|
|
9
|
-
describe('NotesRenderer', () => {
|
|
10
|
-
let renderer: NotesRenderer;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
renderer = new NotesRenderer();
|
|
14
|
-
jest.useFakeTimers().setSystemTime(new Date('2026-02-27'));
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
jest.useRealTimers();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const makeChange = (priority: 'breaking' | 'prominent' | 'context', title: string, type: string): ClassifiedChange => ({
|
|
22
|
-
change: {
|
|
23
|
-
specName: '065-test',
|
|
24
|
-
taskTitle: title,
|
|
25
|
-
taskType: 'Implementation',
|
|
26
|
-
whatWasDone: `Implemented ${title.toLowerCase()}`,
|
|
27
|
-
whyItMatters: 'It matters',
|
|
28
|
-
keyChanges: ['change'],
|
|
29
|
-
impact: ['impact'],
|
|
30
|
-
} as ExtractedChange,
|
|
31
|
-
priority,
|
|
32
|
-
deliverableType: type,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const makeRecommendation = (changes: ClassifiedChange[]): PipelineRecommendation => ({
|
|
36
|
-
currentVersion: '7.0.0',
|
|
37
|
-
recommendedVersion: '8.0.0',
|
|
38
|
-
bumpType: 'major',
|
|
39
|
-
rationale: 'Breaking changes detected',
|
|
40
|
-
confidence: 0.9,
|
|
41
|
-
changes,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('public notes', () => {
|
|
45
|
-
it('should include breaking and prominent but not context', () => {
|
|
46
|
-
const rec = makeRecommendation([
|
|
47
|
-
makeChange('breaking', 'New Token', 'Token'),
|
|
48
|
-
makeChange('prominent', 'Build Tool', 'Tool'),
|
|
49
|
-
makeChange('context', 'Governance Update', 'Governance'),
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
const result = renderer.render(rec);
|
|
53
|
-
|
|
54
|
-
expect(result.public).toContain('New Token');
|
|
55
|
-
expect(result.public).toContain('Build Tool');
|
|
56
|
-
expect(result.public).not.toContain('Governance Update');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should show fallback message when no public changes', () => {
|
|
60
|
-
const rec = makeRecommendation([
|
|
61
|
-
makeChange('context', 'Internal cleanup', 'Other'),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
const result = renderer.render(rec);
|
|
65
|
-
|
|
66
|
-
expect(result.public).toContain('No consumer-facing changes');
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('internal notes', () => {
|
|
71
|
-
it('should include all tiers', () => {
|
|
72
|
-
const rec = makeRecommendation([
|
|
73
|
-
makeChange('breaking', 'New Token', 'Token'),
|
|
74
|
-
makeChange('prominent', 'Build Tool', 'Tool'),
|
|
75
|
-
makeChange('context', 'Governance Update', 'Governance'),
|
|
76
|
-
]);
|
|
77
|
-
|
|
78
|
-
const result = renderer.render(rec);
|
|
79
|
-
|
|
80
|
-
expect(result.internal).toContain('New Token');
|
|
81
|
-
expect(result.internal).toContain('Build Tool');
|
|
82
|
-
expect(result.internal).toContain('Governance Update');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('markdown structure', () => {
|
|
87
|
-
it('should include header with version, date, and bump type', () => {
|
|
88
|
-
const rec = makeRecommendation([makeChange('breaking', 'Change', 'Token')]);
|
|
89
|
-
const result = renderer.render(rec);
|
|
90
|
-
|
|
91
|
-
expect(result.public).toContain('# Release 8.0.0');
|
|
92
|
-
expect(result.public).toContain('**Date**: 2026-02-27');
|
|
93
|
-
expect(result.public).toContain('**Previous**: 7.0.0');
|
|
94
|
-
expect(result.public).toContain('**Bump**: major');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should render tier headings', () => {
|
|
98
|
-
const rec = makeRecommendation([
|
|
99
|
-
makeChange('breaking', 'Token Change', 'Token'),
|
|
100
|
-
makeChange('prominent', 'Tool Change', 'Tool'),
|
|
101
|
-
]);
|
|
102
|
-
|
|
103
|
-
const result = renderer.render(rec);
|
|
104
|
-
|
|
105
|
-
expect(result.public).toContain('## 🔴 Breaking / Consumer-Facing');
|
|
106
|
-
expect(result.public).toContain('## 🟡 Ecosystem Changes');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should render change entries with title, type, and description', () => {
|
|
110
|
-
const rec = makeRecommendation([makeChange('breaking', 'New Color Token', 'Token')]);
|
|
111
|
-
const result = renderer.render(rec);
|
|
112
|
-
|
|
113
|
-
expect(result.public).toContain('- **New Color Token** *(Token)*');
|
|
114
|
-
expect(result.public).toContain('Implemented new color token');
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('json output', () => {
|
|
119
|
-
it('should include structured release data', () => {
|
|
120
|
-
const changes = [makeChange('breaking', 'Change', 'Token')];
|
|
121
|
-
const rec = makeRecommendation(changes);
|
|
122
|
-
const result = renderer.render(rec);
|
|
123
|
-
|
|
124
|
-
expect(result.json.version).toBe('8.0.0');
|
|
125
|
-
expect(result.json.previousVersion).toBe('7.0.0');
|
|
126
|
-
expect(result.json.date).toBe('2026-02-27');
|
|
127
|
-
expect(result.json.changes).toEqual(changes);
|
|
128
|
-
expect(result.json.recommendation).toBe(rec);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('empty changes', () => {
|
|
133
|
-
it('should handle recommendation with no changes', () => {
|
|
134
|
-
const rec = makeRecommendation([]);
|
|
135
|
-
const result = renderer.render(rec);
|
|
136
|
-
|
|
137
|
-
expect(result.public).toContain('No consumer-facing changes');
|
|
138
|
-
expect(result.internal).toContain('No changes in this release');
|
|
139
|
-
expect(result.json.changes).toEqual([]);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|