performance-budget-enforcer 1.0.1 → 1.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/README.md +114 -7
- package/dist/cli/index.js +7 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/github/reporter.d.ts.map +1 -1
- package/dist/github/reporter.js +42 -17
- package/dist/github/reporter.js.map +1 -1
- package/dist/github/slack.d.ts +16 -0
- package/dist/github/slack.d.ts.map +1 -0
- package/dist/github/slack.js +94 -0
- package/dist/github/slack.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/index.ts +8 -1
- package/src/github/reporter.ts +47 -17
- package/src/github/slack.ts +112 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ A CI plugin that scans React/Next.js builds, detects bundle size regressions, an
|
|
|
9
9
|
- 🚫 Fails CI when budgets are exceeded
|
|
10
10
|
- 📈 Compares against baseline for regression detection
|
|
11
11
|
- 💬 Posts PR comments with detailed reports
|
|
12
|
+
- 📢 Sends Slack notifications on budget failures
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -29,18 +30,28 @@ This creates a `perf-budget.json` config file.
|
|
|
29
30
|
### Analyze bundles
|
|
30
31
|
|
|
31
32
|
```bash
|
|
32
|
-
npx perf-budget analyze --dir
|
|
33
|
+
npx perf-budget analyze --dir .next
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
### Check budget
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
|
-
npx perf-budget check --dir
|
|
39
|
+
npx perf-budget check --dir .next
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Regression detection with baseline
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# First run: save baseline
|
|
46
|
+
npx perf-budget analyze --dir .next -o baseline.json
|
|
47
|
+
|
|
48
|
+
# Later runs: compare against baseline
|
|
49
|
+
npx perf-budget check --dir .next --baseline baseline.json
|
|
39
50
|
```
|
|
40
51
|
|
|
41
52
|
## Configuration
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
Edit `perf-budget.json`:
|
|
44
55
|
|
|
45
56
|
```json
|
|
46
57
|
{
|
|
@@ -55,14 +66,110 @@ Create `perf-budget.json` in your project root:
|
|
|
55
66
|
}
|
|
56
67
|
```
|
|
57
68
|
|
|
58
|
-
|
|
69
|
+
- All sizes are in **bytes**
|
|
70
|
+
- `maxTotalSize`: Maximum total bundle size
|
|
71
|
+
- `maxGzippedSize`: Maximum gzipped size
|
|
72
|
+
- `maxChunkSize`: Maximum size for any single chunk
|
|
73
|
+
- `maxDependencySize`: Maximum size for any dependency
|
|
74
|
+
- `thresholds.error`: % growth vs baseline that triggers CI failure (default: 20%)
|
|
75
|
+
|
|
76
|
+
## GitHub Integration
|
|
77
|
+
|
|
78
|
+
The tool automatically posts PR comments when budgets fail.
|
|
79
|
+
|
|
80
|
+
Set these environment variables in your CI:
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
- name: Performance Budget Check
|
|
84
|
+
run: npx perf-budget check --dir .next
|
|
85
|
+
env:
|
|
86
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
87
|
+
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
88
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Slack Integration
|
|
92
|
+
|
|
93
|
+
Get a webhook URL: https://api.slack.com/messaging/webhooks
|
|
94
|
+
|
|
95
|
+
Add environment variable:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
- name: Performance Budget Check
|
|
99
|
+
run: npx perf-budget check --dir .next
|
|
100
|
+
env:
|
|
101
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
102
|
+
SLACK_CHANNEL: #performance # optional
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Optional env vars:
|
|
106
|
+
- `SLACK_WEBHOOK_URL` - Required webhook URL
|
|
107
|
+
- `SLACK_CHANNEL` - Override default channel
|
|
108
|
+
- `SLACK_USERNAME` - Custom username
|
|
109
|
+
- `SLACK_ICON_EMOJI` - Custom emoji (default: :package:)
|
|
59
110
|
|
|
60
111
|
## GitHub Actions Integration
|
|
61
112
|
|
|
62
|
-
|
|
113
|
+
```yaml
|
|
114
|
+
name: Performance Budget
|
|
115
|
+
|
|
116
|
+
on:
|
|
117
|
+
push:
|
|
118
|
+
branches: [main]
|
|
119
|
+
pull_request:
|
|
120
|
+
branches: [main]
|
|
121
|
+
|
|
122
|
+
jobs:
|
|
123
|
+
perf-budget:
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
|
|
128
|
+
- uses: actions/setup-node@v4
|
|
129
|
+
with:
|
|
130
|
+
node-version: '20'
|
|
131
|
+
cache: 'npm'
|
|
132
|
+
|
|
133
|
+
- name: Install dependencies
|
|
134
|
+
run: npm ci
|
|
135
|
+
|
|
136
|
+
- name: Build
|
|
137
|
+
run: npm run build
|
|
138
|
+
|
|
139
|
+
- name: Download baseline
|
|
140
|
+
if: github.event_name == 'pull_request'
|
|
141
|
+
uses: actions/download-artifact@v4
|
|
142
|
+
with:
|
|
143
|
+
name: bundle-baseline
|
|
144
|
+
path: .
|
|
145
|
+
continue-on-error: true
|
|
146
|
+
|
|
147
|
+
- name: Performance Budget Check
|
|
148
|
+
run: npx perf-budget check --dir .next
|
|
149
|
+
env:
|
|
150
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
151
|
+
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
152
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
153
|
+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
154
|
+
|
|
155
|
+
- name: Upload baseline
|
|
156
|
+
if: github.ref == 'refs/heads/main'
|
|
157
|
+
uses: actions/upload-artifact@v4
|
|
158
|
+
with:
|
|
159
|
+
name: bundle-baseline
|
|
160
|
+
path: baseline.json
|
|
161
|
+
```
|
|
63
162
|
|
|
64
163
|
## Commands
|
|
65
164
|
|
|
66
|
-
- `perf-budget analyze` - Analyze bundle sizes
|
|
67
|
-
- `perf-budget check` - Check against budget
|
|
165
|
+
- `perf-budget analyze [options]` - Analyze bundle sizes
|
|
166
|
+
- `perf-budget check [options]` - Check against budget
|
|
68
167
|
- `perf-budget init` - Create config file
|
|
168
|
+
|
|
169
|
+
### Options
|
|
170
|
+
|
|
171
|
+
- `-d, --dir <path>` - Build directory (default: ./build)
|
|
172
|
+
- `-c, --config <path>` - Config file (default: ./perf-budget.json)
|
|
173
|
+
- `-b, --baseline <path>` - Baseline file for regression detection
|
|
174
|
+
- `-o, --output <path>` - Output JSON file
|
|
175
|
+
- `--fail` - Exit with code 1 if budget exceeded (default: true)
|
package/dist/cli/index.js
CHANGED
|
@@ -45,11 +45,12 @@ const fs = __importStar(require("fs"));
|
|
|
45
45
|
const analyzer_1 = require("../lib/analyzer");
|
|
46
46
|
const budget_1 = require("../lib/budget");
|
|
47
47
|
const reporter_1 = require("../github/reporter");
|
|
48
|
+
const slack_1 = require("../github/slack");
|
|
48
49
|
const program = new commander_1.Command();
|
|
49
50
|
program
|
|
50
51
|
.name('perf-budget')
|
|
51
52
|
.description('Frontend Performance Budget Enforcer - CI Plugin for React/Next.js')
|
|
52
|
-
.version('1.
|
|
53
|
+
.version('1.1.0');
|
|
53
54
|
program
|
|
54
55
|
.command('analyze')
|
|
55
56
|
.description('Analyze bundle sizes')
|
|
@@ -137,6 +138,11 @@ program
|
|
|
137
138
|
const reporter = new reporter_1.GitHubReporter(githubConfig);
|
|
138
139
|
await reporter.report(result);
|
|
139
140
|
}
|
|
141
|
+
const slackConfig = (0, slack_1.getSlackEnv)();
|
|
142
|
+
if (slackConfig) {
|
|
143
|
+
const slackReporter = new slack_1.SlackReporter(slackConfig);
|
|
144
|
+
await slackReporter.report(result);
|
|
145
|
+
}
|
|
140
146
|
}
|
|
141
147
|
if (!result.passed && options.fail) {
|
|
142
148
|
spinner.fail('Budget exceeded!');
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,yCAAoC;AACpC,kDAA0B;AAC1B,8CAAsB;AACtB,2CAA6B;AAC7B,uCAAyB;AACzB,8CAA2E;AAC3E,0CAA0D;AAC1D,iDAAkE;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/github/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAI1B,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjD,OAAO,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/github/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,YAAY;IAI1B,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjD,OAAO,CAAC,aAAa;YAyBP,WAAW;CA4D1B;AAED,wBAAgB,YAAY,IAAI,YAAY,GAAG,IAAI,CAiBlD"}
|
package/dist/github/reporter.js
CHANGED
|
@@ -32,36 +32,61 @@ class GitHubReporter {
|
|
|
32
32
|
comment += `| ${v.type} | ${v.name} | ${(0, analyzer_1.formatSize)(v.current)} | ${(0, analyzer_1.formatSize)(v.limit)} | +${v.percentageOver.toFixed(1)}% |\n`;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
comment += `\n---\n*Powered by [performance-budget-enforcer](https://github.com/performance-budget-enforcer)*`;
|
|
35
36
|
return comment;
|
|
36
37
|
}
|
|
37
38
|
async postComment(body) {
|
|
38
39
|
const { execSync } = require('child_process');
|
|
40
|
+
const prIdQuery = JSON.stringify({
|
|
41
|
+
query: `query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
42
|
+
repository(owner: $owner, name: $repo) {
|
|
43
|
+
pullRequest(number: $prNumber) {
|
|
44
|
+
id
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}`,
|
|
48
|
+
variables: {
|
|
49
|
+
owner: this.config.owner,
|
|
50
|
+
repo: this.config.repo,
|
|
51
|
+
prNumber: this.config.prNumber
|
|
52
|
+
}
|
|
53
|
+
});
|
|
39
54
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
const prIdCmd = `curl -s -X POST -H "Authorization: bearer ${this.config.token}" -H "Content-Type: application/json" -d '${prIdQuery}' https://api.github.com/graphql`;
|
|
56
|
+
const prIdResult = execSync(prIdCmd, { encoding: 'utf-8' });
|
|
57
|
+
const prIdData = JSON.parse(prIdResult);
|
|
58
|
+
if (!prIdData.data?.repository?.pullRequest?.id) {
|
|
59
|
+
console.log('Could not find PR ID. Make sure GITHUB_PR_NUMBER is set correctly.');
|
|
60
|
+
console.log(body);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const prId = prIdData.data.repository.pullRequest.id;
|
|
64
|
+
const addCommentMutation = JSON.stringify({
|
|
65
|
+
query: `mutation($input: AddCommentInput!) {
|
|
66
|
+
addComment(input: $input) {
|
|
43
67
|
commentEdge {
|
|
44
68
|
node {
|
|
45
69
|
id
|
|
70
|
+
body
|
|
46
71
|
}
|
|
47
72
|
}
|
|
48
73
|
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log(`Posting comment to PR #${this.config.prNumber}...`);
|
|
61
|
-
console.log(body);
|
|
74
|
+
}`,
|
|
75
|
+
variables: {
|
|
76
|
+
input: {
|
|
77
|
+
subjectId: prId,
|
|
78
|
+
body: body
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const commentCmd = `curl -s -X POST -H "Authorization: bearer ${this.config.token}" -H "Content-Type: application/json" -d '${addCommentMutation}' https://api.github.com/graphql`;
|
|
83
|
+
execSync(commentCmd, { encoding: 'utf-8' });
|
|
84
|
+
console.log(`✅ Posted comment to PR #${this.config.prNumber}`);
|
|
62
85
|
}
|
|
63
86
|
catch (error) {
|
|
64
|
-
console.error('Failed to post GitHub comment:', error);
|
|
87
|
+
console.error('Failed to post GitHub comment:', error.message);
|
|
88
|
+
console.log('Comment preview:');
|
|
89
|
+
console.log(body);
|
|
65
90
|
}
|
|
66
91
|
}
|
|
67
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/github/reporter.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/github/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BudgetResult } from '../lib/types';
|
|
2
|
+
export interface SlackConfig {
|
|
3
|
+
webhookUrl: string;
|
|
4
|
+
channel?: string;
|
|
5
|
+
username?: string;
|
|
6
|
+
iconEmoji?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class SlackReporter {
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config: SlackConfig);
|
|
11
|
+
report(result: BudgetResult): Promise<void>;
|
|
12
|
+
private formatPayload;
|
|
13
|
+
private send;
|
|
14
|
+
}
|
|
15
|
+
export declare function getSlackEnv(): SlackConfig | null;
|
|
16
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/github/slack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAc;gBAEhB,MAAM,EAAE,WAAW;IAIzB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD,OAAO,CAAC,aAAa;YA+DP,IAAI;CAWnB;AAED,wBAAgB,WAAW,IAAI,WAAW,GAAG,IAAI,CAahD"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SlackReporter = void 0;
|
|
4
|
+
exports.getSlackEnv = getSlackEnv;
|
|
5
|
+
const analyzer_1 = require("../lib/analyzer");
|
|
6
|
+
class SlackReporter {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
async report(result) {
|
|
11
|
+
const payload = this.formatPayload(result);
|
|
12
|
+
await this.send(payload);
|
|
13
|
+
}
|
|
14
|
+
formatPayload(result) {
|
|
15
|
+
const status = result.passed ? '✅ *PASSED*' : '❌ *FAILED*';
|
|
16
|
+
let blocks = [
|
|
17
|
+
{
|
|
18
|
+
type: 'header',
|
|
19
|
+
text: {
|
|
20
|
+
type: 'plain_text',
|
|
21
|
+
text: '📦 Bundle Size Report',
|
|
22
|
+
emoji: true
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'section',
|
|
27
|
+
fields: [
|
|
28
|
+
{
|
|
29
|
+
type: 'mrkdwn',
|
|
30
|
+
text: `*Status:*\n${status}`
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'mrkdwn',
|
|
34
|
+
text: `*Total Size:*\n${(0, analyzer_1.formatSize)(result.totalSize)}`
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'mrkdwn',
|
|
38
|
+
text: `*Gzipped:*\n${(0, analyzer_1.formatSize)(result.totalGzipped)}`
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
if (!result.passed && result.violations.length > 0) {
|
|
44
|
+
const violationTexts = result.violations.slice(0, 10).map(v => `• *${v.name}*: ${(0, analyzer_1.formatSize)(v.current)} (limit: ${(0, analyzer_1.formatSize)(v.limit)}, +${v.percentageOver.toFixed(1)}%)`).join('\n');
|
|
45
|
+
blocks.push({
|
|
46
|
+
type: 'section',
|
|
47
|
+
text: {
|
|
48
|
+
type: 'mrkdwn',
|
|
49
|
+
text: `*⚠️ Budget Violations:*\n${violationTexts}`
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
blocks.push({
|
|
54
|
+
type: 'context',
|
|
55
|
+
elements: [
|
|
56
|
+
{
|
|
57
|
+
type: 'mrkdwn',
|
|
58
|
+
text: `Triggered by performance-budget-enforcer`
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
return JSON.stringify({
|
|
63
|
+
channel: this.config.channel,
|
|
64
|
+
username: this.config.username || 'Performance Budget',
|
|
65
|
+
icon_emoji: this.config.iconEmoji || ':package:',
|
|
66
|
+
blocks
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async send(payload) {
|
|
70
|
+
const { execSync } = require('child_process');
|
|
71
|
+
try {
|
|
72
|
+
const cmd = `curl -s -X POST -H 'Content-Type: application/json' -d '${payload}' ${this.config.webhookUrl}`;
|
|
73
|
+
execSync(cmd, { encoding: 'utf-8' });
|
|
74
|
+
console.log('✅ Slack notification sent');
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('Failed to send Slack notification:', error.message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.SlackReporter = SlackReporter;
|
|
82
|
+
function getSlackEnv() {
|
|
83
|
+
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
|
|
84
|
+
if (!webhookUrl) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
webhookUrl,
|
|
89
|
+
channel: process.env.SLACK_CHANNEL,
|
|
90
|
+
username: process.env.SLACK_USERNAME,
|
|
91
|
+
iconEmoji: process.env.SLACK_ICON_EMOJI
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=slack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/github/slack.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { BundleAnalyzer, formatSize, saveAnalysis, loadAnalysis } from './lib/analyzer';
|
|
2
2
|
export { BudgetChecker, loadConfig } from './lib/budget';
|
|
3
3
|
export { GitHubReporter, getGitHubEnv } from './github/reporter';
|
|
4
|
+
export { SlackReporter, getSlackEnv } from './github/slack';
|
|
4
5
|
export * from './lib/types';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC5D,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.getGitHubEnv = exports.GitHubReporter = exports.loadConfig = exports.BudgetChecker = exports.loadAnalysis = exports.saveAnalysis = exports.formatSize = exports.BundleAnalyzer = void 0;
|
|
17
|
+
exports.getSlackEnv = exports.SlackReporter = exports.getGitHubEnv = exports.GitHubReporter = exports.loadConfig = exports.BudgetChecker = exports.loadAnalysis = exports.saveAnalysis = exports.formatSize = exports.BundleAnalyzer = void 0;
|
|
18
18
|
var analyzer_1 = require("./lib/analyzer");
|
|
19
19
|
Object.defineProperty(exports, "BundleAnalyzer", { enumerable: true, get: function () { return analyzer_1.BundleAnalyzer; } });
|
|
20
20
|
Object.defineProperty(exports, "formatSize", { enumerable: true, get: function () { return analyzer_1.formatSize; } });
|
|
@@ -26,5 +26,8 @@ Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function (
|
|
|
26
26
|
var reporter_1 = require("./github/reporter");
|
|
27
27
|
Object.defineProperty(exports, "GitHubReporter", { enumerable: true, get: function () { return reporter_1.GitHubReporter; } });
|
|
28
28
|
Object.defineProperty(exports, "getGitHubEnv", { enumerable: true, get: function () { return reporter_1.getGitHubEnv; } });
|
|
29
|
+
var slack_1 = require("./github/slack");
|
|
30
|
+
Object.defineProperty(exports, "SlackReporter", { enumerable: true, get: function () { return slack_1.SlackReporter; } });
|
|
31
|
+
Object.defineProperty(exports, "getSlackEnv", { enumerable: true, get: function () { return slack_1.getSlackEnv; } });
|
|
29
32
|
__exportStar(require("./lib/types"), exports);
|
|
30
33
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAwF;AAA/E,0GAAA,cAAc,OAAA;AAAE,sGAAA,UAAU,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,wGAAA,YAAY,OAAA;AAC/D,uCAAyD;AAAhD,uGAAA,aAAa,OAAA;AAAE,oGAAA,UAAU,OAAA;AAClC,8CAAiE;AAAxD,0GAAA,cAAc,OAAA;AAAE,wGAAA,YAAY,OAAA;AACrC,8CAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAwF;AAA/E,0GAAA,cAAc,OAAA;AAAE,sGAAA,UAAU,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,wGAAA,YAAY,OAAA;AAC/D,uCAAyD;AAAhD,uGAAA,aAAa,OAAA;AAAE,oGAAA,UAAU,OAAA;AAClC,8CAAiE;AAAxD,0GAAA,cAAc,OAAA;AAAE,wGAAA,YAAY,OAAA;AACrC,wCAA4D;AAAnD,sGAAA,aAAa,OAAA;AAAE,oGAAA,WAAW,OAAA;AACnC,8CAA4B"}
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import { BundleAnalyzer, formatSize, saveAnalysis } from '../lib/analyzer';
|
|
9
9
|
import { BudgetChecker, loadConfig } from '../lib/budget';
|
|
10
10
|
import { GitHubReporter, getGitHubEnv } from '../github/reporter';
|
|
11
|
+
import { SlackReporter, getSlackEnv } from '../github/slack';
|
|
11
12
|
import { BundleAnalysis, BudgetConfig } from '../lib/types';
|
|
12
13
|
|
|
13
14
|
const program = new Command();
|
|
@@ -15,7 +16,7 @@ const program = new Command();
|
|
|
15
16
|
program
|
|
16
17
|
.name('perf-budget')
|
|
17
18
|
.description('Frontend Performance Budget Enforcer - CI Plugin for React/Next.js')
|
|
18
|
-
.version('1.
|
|
19
|
+
.version('1.1.0');
|
|
19
20
|
|
|
20
21
|
program
|
|
21
22
|
.command('analyze')
|
|
@@ -117,6 +118,12 @@ program
|
|
|
117
118
|
const reporter = new GitHubReporter(githubConfig);
|
|
118
119
|
await reporter.report(result);
|
|
119
120
|
}
|
|
121
|
+
|
|
122
|
+
const slackConfig = getSlackEnv();
|
|
123
|
+
if (slackConfig) {
|
|
124
|
+
const slackReporter = new SlackReporter(slackConfig);
|
|
125
|
+
await slackReporter.report(result);
|
|
126
|
+
}
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
if (!result.passed && options.fail) {
|
package/src/github/reporter.ts
CHANGED
|
@@ -45,39 +45,69 @@ export class GitHubReporter {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
comment += `\n---\n*Powered by [performance-budget-enforcer](https://github.com/performance-budget-enforcer)*`;
|
|
49
|
+
|
|
48
50
|
return comment;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
private async postComment(body: string): Promise<void> {
|
|
52
54
|
const { execSync } = require('child_process');
|
|
53
|
-
|
|
55
|
+
|
|
56
|
+
const prIdQuery = JSON.stringify({
|
|
57
|
+
query: `query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
58
|
+
repository(owner: $owner, name: $repo) {
|
|
59
|
+
pullRequest(number: $prNumber) {
|
|
60
|
+
id
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}`,
|
|
64
|
+
variables: {
|
|
65
|
+
owner: this.config.owner,
|
|
66
|
+
repo: this.config.repo,
|
|
67
|
+
prNumber: this.config.prNumber
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
54
71
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
const prIdCmd = `curl -s -X POST -H "Authorization: bearer ${this.config.token}" -H "Content-Type: application/json" -d '${prIdQuery}' https://api.github.com/graphql`;
|
|
73
|
+
const prIdResult = execSync(prIdCmd, { encoding: 'utf-8' });
|
|
74
|
+
const prIdData = JSON.parse(prIdResult);
|
|
75
|
+
|
|
76
|
+
if (!prIdData.data?.repository?.pullRequest?.id) {
|
|
77
|
+
console.log('Could not find PR ID. Make sure GITHUB_PR_NUMBER is set correctly.');
|
|
78
|
+
console.log(body);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const prId = prIdData.data.repository.pullRequest.id;
|
|
83
|
+
|
|
84
|
+
const addCommentMutation = JSON.stringify({
|
|
85
|
+
query: `mutation($input: AddCommentInput!) {
|
|
86
|
+
addComment(input: $input) {
|
|
58
87
|
commentEdge {
|
|
59
88
|
node {
|
|
60
89
|
id
|
|
90
|
+
body
|
|
61
91
|
}
|
|
62
92
|
}
|
|
63
93
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
repository(owner: $owner, name: $repo) {
|
|
70
|
-
pullRequest(number: $prNumber) {
|
|
71
|
-
id
|
|
72
|
-
}
|
|
94
|
+
}`,
|
|
95
|
+
variables: {
|
|
96
|
+
input: {
|
|
97
|
+
subjectId: prId,
|
|
98
|
+
body: body
|
|
73
99
|
}
|
|
74
100
|
}
|
|
75
|
-
|
|
101
|
+
});
|
|
76
102
|
|
|
77
|
-
|
|
103
|
+
const commentCmd = `curl -s -X POST -H "Authorization: bearer ${this.config.token}" -H "Content-Type: application/json" -d '${addCommentMutation}' https://api.github.com/graphql`;
|
|
104
|
+
|
|
105
|
+
execSync(commentCmd, { encoding: 'utf-8' });
|
|
106
|
+
console.log(`✅ Posted comment to PR #${this.config.prNumber}`);
|
|
107
|
+
} catch (error: any) {
|
|
108
|
+
console.error('Failed to post GitHub comment:', error.message);
|
|
109
|
+
console.log('Comment preview:');
|
|
78
110
|
console.log(body);
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('Failed to post GitHub comment:', error);
|
|
81
111
|
}
|
|
82
112
|
}
|
|
83
113
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { BudgetResult } from '../lib/types';
|
|
2
|
+
import { formatSize } from '../lib/analyzer';
|
|
3
|
+
|
|
4
|
+
export interface SlackConfig {
|
|
5
|
+
webhookUrl: string;
|
|
6
|
+
channel?: string;
|
|
7
|
+
username?: string;
|
|
8
|
+
iconEmoji?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SlackReporter {
|
|
12
|
+
private config: SlackConfig;
|
|
13
|
+
|
|
14
|
+
constructor(config: SlackConfig) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async report(result: BudgetResult): Promise<void> {
|
|
19
|
+
const payload = this.formatPayload(result);
|
|
20
|
+
await this.send(payload);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private formatPayload(result: BudgetResult): string {
|
|
24
|
+
const status = result.passed ? '✅ *PASSED*' : '❌ *FAILED*';
|
|
25
|
+
|
|
26
|
+
let blocks: any[] = [
|
|
27
|
+
{
|
|
28
|
+
type: 'header',
|
|
29
|
+
text: {
|
|
30
|
+
type: 'plain_text',
|
|
31
|
+
text: '📦 Bundle Size Report',
|
|
32
|
+
emoji: true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'section',
|
|
37
|
+
fields: [
|
|
38
|
+
{
|
|
39
|
+
type: 'mrkdwn',
|
|
40
|
+
text: `*Status:*\n${status}`
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'mrkdwn',
|
|
44
|
+
text: `*Total Size:*\n${formatSize(result.totalSize)}`
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'mrkdwn',
|
|
48
|
+
text: `*Gzipped:*\n${formatSize(result.totalGzipped)}`
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
if (!result.passed && result.violations.length > 0) {
|
|
55
|
+
const violationTexts = result.violations.slice(0, 10).map(v =>
|
|
56
|
+
`• *${v.name}*: ${formatSize(v.current)} (limit: ${formatSize(v.limit)}, +${v.percentageOver.toFixed(1)}%)`
|
|
57
|
+
).join('\n');
|
|
58
|
+
|
|
59
|
+
blocks.push({
|
|
60
|
+
type: 'section',
|
|
61
|
+
text: {
|
|
62
|
+
type: 'mrkdwn',
|
|
63
|
+
text: `*⚠️ Budget Violations:*\n${violationTexts}`
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
blocks.push({
|
|
69
|
+
type: 'context',
|
|
70
|
+
elements: [
|
|
71
|
+
{
|
|
72
|
+
type: 'mrkdwn',
|
|
73
|
+
text: `Triggered by performance-budget-enforcer`
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return JSON.stringify({
|
|
79
|
+
channel: this.config.channel,
|
|
80
|
+
username: this.config.username || 'Performance Budget',
|
|
81
|
+
icon_emoji: this.config.iconEmoji || ':package:',
|
|
82
|
+
blocks
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async send(payload: string): Promise<void> {
|
|
87
|
+
const { execSync } = require('child_process');
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const cmd = `curl -s -X POST -H 'Content-Type: application/json' -d '${payload}' ${this.config.webhookUrl}`;
|
|
91
|
+
execSync(cmd, { encoding: 'utf-8' });
|
|
92
|
+
console.log('✅ Slack notification sent');
|
|
93
|
+
} catch (error: any) {
|
|
94
|
+
console.error('Failed to send Slack notification:', error.message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getSlackEnv(): SlackConfig | null {
|
|
100
|
+
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
|
|
101
|
+
|
|
102
|
+
if (!webhookUrl) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
webhookUrl,
|
|
108
|
+
channel: process.env.SLACK_CHANNEL,
|
|
109
|
+
username: process.env.SLACK_USERNAME,
|
|
110
|
+
iconEmoji: process.env.SLACK_ICON_EMOJI
|
|
111
|
+
};
|
|
112
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BundleAnalyzer, formatSize, saveAnalysis, loadAnalysis } from './lib/analyzer';
|
|
2
2
|
export { BudgetChecker, loadConfig } from './lib/budget';
|
|
3
3
|
export { GitHubReporter, getGitHubEnv } from './github/reporter';
|
|
4
|
+
export { SlackReporter, getSlackEnv } from './github/slack';
|
|
4
5
|
export * from './lib/types';
|